/workshop-intro-to-graphql

Learn the basics of GraphQL. Plenty of hands-on experience covering: a React client interacting with Astra DB using the Netlify stack; usage of playgrounds and tooling; running your own Java GraphQL backend based on the Netflix DGS framework.

Primary LanguageJavaScript

🎓 Introduction to GraphQL + React + Java + Astra DB

Gitpod ready-to-code License Apache2 Discord

50 minutes, Beginner/Intermediate, Start Building

Both a simple graphQL enabled ReactJS app built using create-react-app AND a simple Java backend graphQL service built with Spring Initializr and using The Netflix DGS framework PLUS Astra DB hooked up and ready to rock! 😻

This is a companion to our Netflix Clone using Astra DB and GraphQL workshop and is essentially a "prologue" to that content. Once complete, feel free to to go deploy a Netflix clone using what you learned here.

Finally, this content uses React/JS concepts. If you are not familiar with those or need a refresher, take a look HERE to get up to date.

The materials have been built by the DataStax developer advocates team.

GraphQL Logo

🎯 Objectives

  • An overview of what GraphQL is and what makes it cool
  • What differs between GraphQL and other APIs (such as REST), including their pros/cons
  • Hands-on examples of GraphQL queries and mutations
  • How to build GraphQL APIs for mobile and web applications
  • Setting up your Astra DB to store application data via GraphQL

ℹ️ Frequently asked questions ℹ️

1️⃣ Can I run the code for this workshop on my local computer instead of using Gitpod?

There is nothing preventing you from running the workshop on your own machine. If you do so, you will need the following:

  1. node 15 or 16 and npm 7 or later
  2. netlify-cli (use "npm install -g netlify-cli")

You will have to adapt commands and paths based on your environment (including digging into file ".gitpod.yml") and install the dependencies by yourself. We won't provide support to keep on track with schedule. However, we will do our best to give you the info you need to be successful. This is considered a more advanced path to take.

2️⃣ What other prerequisites are there?
  • You will need a github account
  • You should use Chrome or Firefox (other browsers might have trouble displaying Gitpod correctly)
  • You will need an Astra DB account, but we'll cover that in the exercises

3️⃣ Do I need to pay for anything for this workshop?
No. All tools and services we provide here are FREE. FREE not only during the session but also afterwards.

4️⃣ Will I get a certificate if I attend this workshop?
Attending the session is not enough. You need to complete the homework detailed below and you will get a nice badge that you can share on linkedin or anywhere else (open badge specification).

Materials for the Session

It doesn't matter if you join our workshop live or you prefer to do at your own pace, we have you covered. In this repository, you'll find everything you need for this workshop:

Homework

Don't forget to complete your upgrade and get your verified skill badge! Finish and submit your homework!

  1. Complete the practice steps from this repository, as described below, to the end;
  2. Insert (mutate) a new show or a new genre of your choice in the database;
  3. Take a single screenshot of the React app with all of the working Astra DB sections and showing the entry you just added;
  4. Submit your homework here.

That's it, done. We will then grade the submissions: expect an email in a few days!

Let's start

Extra resources

graphql.org - The first place to learn about GraphQL

The Netflix DGS framework Tutorial - Java/Spring GraphQL backend (used to generate this code)

Spring Initializr - Used in the ^above tutorial to generate the Java/Spring backend starter

GraphiQL - GraphQL IDE included with The Netflix DGS Framework

Apollo client - Awesome GraphQL client for React/JS (not used here, but really solid, Netflix uses this)

Top 7 GraphQL IDEs - A nice collection of cool GraphQL IDEs to use

create-react-app tutorial - Create a React app from scratch (used to generate this code)

A Beginner's Guide to GraphQL - Ali Spittel's really awesome GraphQL starter video

1. Login or Register to Astra DB and create database

ASTRA DB is the simplest way to run Cassandra with zero operations at all - just push the button and get your cluster. No credit card required, 40M read/write operations and about 80GB storage monthly for free - sufficient to run small production workloads. If you use up your credits the databases will pause, no charge, and you will be given the option to upgrade to a higher tier.

Leveraging Database creation guide create a database. Right-Click the following button with Open in a new TAB.

Field Value
Database Name workshops
Keyspace Name intrographql
Regions Select GOOGLE CLOUD, then an Area close to you, then a region with no LOCK 🔒 icons: the LOCKed regions are the region not accessible to the Free Tier.

ℹ️ Note: If you already have a database workshops, simply add a keyspace intrographql using the Add Keyspace button on the bottom right hand corner of the DB Dashboard page. You may have to "Resume" the database first in case it is in "hibernated" state.

While the database is being created, you will also get a Security token (needed to authenticate with your database and start using it): please IGNORE THIS ONE, as we will be soon creating a new, more powerful token for today.

The status will change from Pending to Active when the database is ready, this usually only takes 2-3 minutes.

2. Create a security token

Note: this step is very important, as the token generated automatically for you with the database lacks some permissions we'll use in the workshop.

Create a token for your app, using the "Database Administrator" role. Keep it handy for later use (best to download it in CSV format, as the values will not be visible afterward). This will provide authentication later when interacting with the database. Today, in particular, we will need the string labeled "token" (the one starting with AstraCS:...).

⚠️ Important

The instructor will show the token creation on screen,
but will then destroy it immediately for security reasons.

3. Launch Gitpod

Gitpod is an 100% online IDE based on Visual Studio Code. To initialize your environment simply click on the button below (CTRL + Click to open in new tab) You will be asked for you github account, as needed.

Warning: for best results, open the link with Chrome or Firefox!

Open in Gitpod

This will bootstrap your demo environment. Be patient, it will take a few minutes as everything loads up.

Note: during loading of the Gitpod environment, a new tab will be tentatively opened with an URL such as https://8080-datastaxdev-[...].gitpod.io/graphiql. Please CHECK YOUR POPUP BLOCKER and allow it before continuing: this will be your GraphiQL interface!

Show me how Gitpod looks like for this workshop

Gitpod starts with a file explorer on the left (1), an editor panel on the top (2), and - in the case of this specific environment - two consoles side by side, one to launch commands and later start the Node app (3) and one busy with running the Java backend (4). On the right you will find a console switcher to easily locate any console and make it active (but even just clicking on the desired console would do the trick).

The shape of Gitpod

4. Experiment with GraphiQL

It just so happens that The Netflix DGS framework comes with GraphiQL already integrated and ready for use. This is a wonderful tool you can use to explore graphQL queries and mutations. Let's experiment with this now!

Note: the GraphiQL should be open already in a new tab for you; in case it isn't for some reason, run this command in a Gitpod console and manually point a new tab to the URL it prints:echo `gp url 8080`/graphiql.

Something to point out here is there is no database just yet. We are powering the graphQL schema via the back-end Java application and the graphQL data is completely hardcoded. Take a look at both ShowsDatafetcher.java and GenresDatafetcher.java located in graphql-backend-examples/src/main/java/com/example/demo to find the simple implementations using DGS annotations @DgsComponent and @DgsQuery.

Show me how to open these files in Gitpod

In the left toolbar, choose the first tool ("Explorer") and navigate the directory to the desired directory; then, clicking on the files will open them in the editor (topmost panel on the right).

Open files in Gitpod

Now, let's try out some graphQL queries

Plug these into the GraphiQL IDE that launched into a new tab from GitPod.

query justTitle {
  shows {
    title
  }
}
query withReleaseYear {
  shows {
    title
    releaseYear
  }
}
query getOneShow {
  shows (titleFilter: "Ozark") {
      title
      releaseYear
  }
}
query ShowsAndGenres {
  shows {
    title
    releaseYear
  }
  genres {
    value
  }
}

GraphiQL queries

Compare the GraphQL schema

The objects known to a GraphQL API are defined starting from its "Schema". In the case of our DGS Java application, the schema is found in graphql-backend-examples/src/main/resources/schema/schema.graphqls. Take a look at its contents: notice the special Query item that defines the possible queries and, after that, the user-defined types available to the API:

type Query {
    shows(titleFilter: String): [Show]
    genres(labelFilter: String): [Genre]
}

type Show {
    title: String
    releaseYear: Int
}

type Genre {
    value: String!
}

5. Experiment with Astra DB's GraphQL Playground

Ok, let's take this a step further and prepare the data layer for our app. At this point you should have already created your Astra DB database. Follow the instructions below to launch the GraphQL Playground provided in Astra DB:

✅ Step 5a: Open GraphQL Playground:

  1. Ensure you are logged on to your Astra account
  2. Click on the "workshops" database on the left (expanding the list if needed)
  3. Click Connect TAB
  4. Click the APIs connection method
  5. Make sure GraphQL API is selected
  6. Locate the link to your GraphQL Playground in the text

Open Astra DB GraphQL Playground image

Click here if you are using the "New Astra Experience" UI

Open Astra DB GraphQL Playground image, new Astra UI

Note: in the following, we will refer to "playground tabs". These are not the tabs in your browser, rather they are tabs within the Playground application, to switch between the (logically distinct) realms of "managing schema" and "managing data in the tables" (more on that later).

Playground tabs VS Browser tabs

✅ Step 5b: Insert the Astra DB Token to run schema queries

In the GraphQL Playground, Populate HTTP HEADER variable x-cassandra-token on the bottom of the page with your token (including the AstraCS: part). This is the "Database Administrator" token you created earlier on the Astra DB dashboard (Step 2 above).

Note: make sure you are on the graphql-schema playground tab in this step, as this image illustrates:

GraphQL Playground and token header, Schema playground tab

Note: the GraphQL Playground starts with a ready-to-use temporary token as the x-cassandra-token header. But we want the queries run in the Playground to be identical to those that the Netlify functions will run from code, so please replace the token with your DB token as instructed.

✅ Step 5c: Create a table in the GraphQL Playground

Run the following mutation in the graphql-schema playground tab, making sure to replace intrographql in the URL if you used a different keyspace name:

  • Copy the following mutation on the left panel
mutation {
  reference_list: createTable(
    keyspaceName:"intrographql",
    tableName:"reference_list",
    ifNotExists:true
    partitionKeys: [ 
      { name: "label", type: {basic: TEXT} }
    ]
    clusteringKeys: [
      { name: "value", type: {basic: TEXT}, order: "ASC" }
    ]
  )
}

Click on the arrow in the middle of the screen to execute the query.

Execute a query in GraphQL Playground

6. Insert data to DB using the GraphQL Playground

✅ Step 6a: Adjust the second playground tab to your keyspace

In the GraphQL playground, switch to the second Playground tab (graphql). Edit the ending of the URL shown within the Playground page from system to the keyspace name intrographql:

GraphQL URL ending

✅ Step 6b: Set the token to run data queries

Populate the HTTP HEADER variable x-cassandra-token on the bottom of the page with your DB token as shown below (Note: you did this for the graphql-schema playground tab, now repeat for the graphql playground tab!)

GraphQL Playground and token header, GraphQL playground tab

✅ Step 6c: Insert genre names with the Playground

In the GraphQL Playground, populate the reference_list table with all values: copy the following mutation on the left panel

mutation insertGenres {
  action: insertreference_list(value: {label:"genre", value:"Action"}) {
    value{value}
  }
  anime: insertreference_list(value: {label:"genre", value:"Anime"}) {
     value{value}
  }
  award: insertreference_list(value: {label:"genre", value:"Award-Winning"}) {
     value{value}
  }
  children: insertreference_list(value: {label:"genre", value:"Children & Family"}) {
     value{value}
  }
  comedies: insertreference_list(value: {label:"genre", value:"Comedies"}) {
     value{value}
  }
  documentaries: insertreference_list(value: {label:"genre", value:"Documentaries"}) {
     value{value}
  }
  drama: insertreference_list(value: {label:"genre", value:"Dramas"}) {
     value{value}
  }
  fantasy: insertreference_list(value: {label:"genre", value:"Fantasy"}) {
     value{value}
  }
  french: insertreference_list(value: {label:"genre", value:"French"}) {
     value{value}
  }
  horror: insertreference_list(value: {label:"genre", value:"Horror"}) {
     value{value}
  }
  independent: insertreference_list(value: {label:"genre", value:"Independent"}) {
     value{value}
  }
  music: insertreference_list(value: {label:"genre", value:"Music & Musicals"}) {
     value{value}
  }
  romance: insertreference_list(value: {label:"genre", value:"Romance"}) {
     value{value}
  }
  scifi: insertreference_list(value: {label:"genre", value:"Sci-Fi"}) {
     value{value}
  }
  thriller: insertreference_list(value: {label:"genre", value:"Thriller"}) {
     value{value}
  }  
}

Click on the arrow in the middle of the screen to execute the query.

7. Retrieve values from DB using the GraphQL Playground

✅ Step 7a: Read genres with a query in the Playground

In the GraphQL Playground (staying on the graphql playground tab), list values from the table with the following query:

query getAllGenre {
    reference_list (value: {label:"genre"}) {
      values {
      	value
      }
    }
}

👁️ Expected output Playground getAllGenre query result

8. Start up React

So far we have executed GraphQL queries and mutations by hand from specific UIs. Now it's time to start the React client app and query the GraphQL endpoints from it!

"Endpoints", two of them. Each GraphQL server exposes a single endpoint for everything, but remember this app will query both the local DGS app and the Astra DB server!

First you need to run a couple commands to get things set up: in your GitPod IDE navigate to the "Client" terminal (it should already be open for you on the bottom left) and make sure you are in the workshop-intro-to-graphql/graphql-client-examples directory. This is where you'll be running the nodejs/React app.

Remind me what is this "client terminal" ...

It is the block labeled as "3". Click on it, or use the switcher (5):

The shape of Gitpod

✅ Step 8a: Execute the following command

npm install -g netlify-cli

This will install the Netlify CLI (command line interface) which our React/JS app uses in conjunction with the serverless functions we've setup to talk to our graphQL endpoints.

✅ Step 8b: Then, execute

netlify dev

This will start the React/JS application and display results from both the Shows and Genres graphQL queries and endpoints we were just experimenting with.

You should see Gitpod's mini-browser opening up by itself and showing the client application wihtin Gitpod.

Note: the client, at this point, should be opened in the mini-browser within Gitpod; to open it manually, run this command in a Gitpod console and point a new tab to the URL it prints:echo `gp url 8888` .

Compare javascript code to our graphQL queries from above

If you take a look at both getShowsBackend.js and getGenresBackend.js located in graphql-client-examples/functions you should notice that both use the same exact graphQL queries that we used above.

const query = `
    query getAllShows {
      shows {
        title
        releaseYear
      }
    }
  `
  const query = `
    query getAllGenres {
      genres {
        value
      }
    }
  `

All of the javascript wrapped around these is simply there to call the graphQL endpoint with the given query and pass the responseBody back to the calling function.

Now for the cool part

Take a look at Shows.js and Genres.js located in graphql-client-examples/src/components/. In both cases they use React state, gqlResult

  const [gqlResult, setGqlResult] = useState(null)

to receive the responseBody from from our graphQL queries, set the React state, and inject the values dynamically into the DOM. Check out the following javascript snippet from Shows.js.

// Asynchronously fetch any "shows" graphQL data from the Java backend
// using the getShowsBackend serverless function to call out to the
// Netflix DGS Java graphQL endpoint
const response = await fetch("/.netlify/functions/getShowsBackend", {
    method: "POST",
})
const responseBody = await response.json()
setGqlResult(responseBody) // on response set our graphQL result state

Notice how the fields (title, releaseYear) match our graphQL Shows schema exactly.

// Finally, if all other checks pass get the data
// from the payload via gqlResult state and inject it into the DOM
// Notice how the payload example below and the fields "title" and "releaseYear" match exactly
// {"data":{"shows":[{"title":"Stranger Things","releaseYear":2016},{"title":"Ozark","releaseYear":2017}...
return gqlResult.data.shows.map(({ title, releaseYear }) => (
    <div key={title}>
        <p>
        {title}: {releaseYear}
        </p>
    </div>
  ));

Notice how the field (value) matches our graphQL Genres schema exactly.

// Finally, if all other checks pass get the data
// from the payload via gqlResult state and inject it into the DOM
// Notice how the payload example below and the field "value" match exactly
// {"data":{"genres":[{"value":"Action"},{"value":"Anime"}...
return gqlResult.data.genres.map(({ value }) => (
    <div key={value}>
        <p>
        {value}
        </p>
    </div>
  ));

9. Hook the database up to the React client app

The next step is to make the client able to retrieve the genres and the shows from the database, by querying Astra DB's GraphQL API. To achieve this, it's time to provide connection details (addresses, secrets) to the serverless Netlify functions which will back the React client.

✅ Step 9a: Initialize Astra CLI

In the GitPod IDE, click on the "Client" terminal to make it active, hit Ctrl-C to stop the running client, if any, and make sure you are in the workshop-intro-to-graphql/graphql-client-examples directory.

Now you will create a .env file with connection info (addresses and secrets) for the Netlify function to be able to reach both the local backend and your Astra DB's GraphQL endpoint. You will use the Astra command-line interface to prepare a dot-env file for you; then you will complete it by adding a line defining the address of the local backend (i.e. the DGS locally-running GraphQL API).

Run the following command and provide your DB Administrator token string (starting with AstraCS:...) when prompted:

astra setup

✅ Step 9b: Configure database credentials

Once you get a "Configuration has been saved" confirmation, proceed with:

astra db create-dotenv workshops -k intrographql
cat .local-backend.env >> .env
gp open .env

The credentials are now all set up: your dot-env file should be now shown in the editor for you to check its contents. You will see several lines pertaining to Astra DB (not all of which will be used by today's client) and, at the end, a single setting about the Java GraphQL API you tested earlier.

Here is how the .env might look like (as a reference, check out the provided .env.sample):

Sample dot-env file

If you are preparing the file manually (i.e. as opposed to using the astra-cli tool), be aware that the only variables needed by the React client are: ASTRA_DB_APPLICATION_TOKEN, ASTRA_DB_GRAPHQL_URL and JAVA_GRAPHQL_ENDPOINT.

✅ Step 9c: Start your React app again

Launch the following command once more:

netlify dev

✅ Step 9d: Verify data load

At this point your app should be running with a bunch of data displayed in the Shows, Genres, and ReferenceList sections, but notice the ShowsByName section displays "Error :("

Error in Shows by name

Can you figure out what's going on here?

Let's break this down.

  • We just added the database configuration and the ReferenceList section is populated which tells us our DB config and graphQL endpoints are configured properly

  • In the GraphQL Playground we added a schema for the reference_list table and added some data to the table, but we never created a schema for the ShowsByName section

  • If you take a look at the getShowsAstra.js script in graphql-client-examples/functions you can see the graphQL being used to query for data

exports.handler = async function (event) {
  const query = `
    query getAllShows {
      show_by_name {
        values {
          title
          releaseYear
        }
      }
    }
  `

✅ Step 9e: Test this query

Go back to the GraphQL graphQL playground tab.

Copy this into the playground and press the "play" button to execute the query. NOTE, you can simply append the query to the end of the list and then choose the query you wish to execute when you hit the "play" button.

query getAllShows {
      show_by_name {
        values {
          title
          releaseYear
        }
      }
    }

GraphQL getAllShows execution

View Results

Notice what happened here. We have a validation error because there is no schema associated with the query we just executed. GraphQL uses a typed validation system so this is something to expect if a query is malformed, missing a schema, or something along those lines. You will want to control for this in your code.

GraphQL "Field undefined" error

✅ Step 9f: Create the missing table

To fix up the schema issue, and resolve the error, create the ShowsByName table with a graphQL mutation to fix the app. Execute the following mutation in the graph-schema Playground tab

mutation CreateShowsTable {
  createTable(
    keyspaceName: "intrographql"
    tableName: "show_by_name"
    partitionKeys: [{
      name: "title", type: {basic:TEXT}
    }]
    values:[{
      name: "releaseYear", type: {basic:INT}
    }]
  )
}

GraphQL CreateShowsTable query

✅ Verify result

Once executed you should see a result like this

GrahQL CreateShowsTable result

✅ Step 9g: Add some data

Now, go back to the graphql playground tab and add the following mutation

mutation insertShows {
  stranger: insertshow_by_name (
    value: {
      title: "Stranger Things",
      releaseYear: 2016}) {
  	value{title}
  }
  ozark: insertshow_by_name (
    value: {
      title: "Ozark",
      releaseYear: 2017}) {
  	value{title}
  }
}

GraphQL "insertShows"

✅ Check the result

GraphQL insertShows, result

✅ Step 9h: Check the client again

Finally, refresh your React app.

Notice this no longer displays an error. Now it correctly displays the data you just inserted (mutated). It might be fun to add some of your own data to this schema and refresh your page.

GraphQL, client showing shows from DB

Play a bit more!

Feel free to experiment with a couple more graphQL queries now that you have some data in the table

Queries usually offer some way to restrict the results returned, in the form of parameters passed to queries. Recall the original getAllShows, repeated here for convenience:

query getAllShows {
  show_by_name {
    values {
      title
      releaseYear
    }
  }
}

Now let's see a way to pass a title parameter to the query and just get matching values (a single entry, in this case):

query getOneShow {
  show_by_name (value: {title: "Ozark"}) {
    values {
      title
      releaseYear
    }
  }
}

The following query, which uses the more general filter syntax, is completely equivalent to the previous one:

query getOneShowF {
  show_by_name(filter: {title: {eq: "Ozark"}}){
    values {
      title
      releaseYear
    }
  }
}

That's it, you did it! Nice job!

We hope this workshop gave you enough information on GraphQL to be dangerous and start you on a journey to using GraphQL in your own apps. Also, don't forget your HOMEWORK.