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.
- 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
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:
- node 15 or 16 and npm 7 or later
- netlify-cli (use "npm install -g netlify-cli")
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).
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:
Don't forget to complete your upgrade and get your verified skill badge! Finish and submit your homework!
- Complete the practice steps from this repository, as described below, to the end;
- Insert (mutate) a new show or a new genre of your choice in the database;
- Take a single screenshot of the React app with all of the working Astra DB sections and showing the entry you just added;
- Submit your homework here.
That's it, done. We will then grade the submissions: expect an email in a few days!
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
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 keyspaceintrographqlusing theAdd Keyspacebutton 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.
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:...).
⚠️ ImportantThe instructor will show the token creation on screen, but will then destroy it immediately for security reasons.
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!
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).
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).
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
  }
}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!
}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:
- Ensure you are logged on to your Astra account
- Click on the "workshops" database on the left (expanding the list if needed)
- Click ConnectTAB
- Click the APIsconnection method
- Make sure GraphQL APIis selected
- Locate the link to your GraphQL Playground in the text
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).
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:
Note: the GraphQL Playground starts with a ready-to-use temporary token as the
x-cassandra-tokenheader. 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.
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.
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:
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!)
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.
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
      }
    }
}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):
npm install -g netlify-cliThis 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.
netlify devThis 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`.
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.
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 stateNotice 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>
  ));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.
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
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):
If you are preparing the file manually (i.e. as opposed to using the
astra-clitool), be aware that the only variables needed by the React client are:ASTRA_DB_APPLICATION_TOKEN,ASTRA_DB_GRAPHQL_URLandJAVA_GRAPHQL_ENDPOINT.
Launch the following command once more:
netlify devAt 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 :("
Let's break this down.
- 
We just added the database configuration and the ReferenceListsection 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_listtable and added some data to the table, but we never created a schema for theShowsByNamesection
- 
If you take a look at the getShowsAstra.jsscript ingraphql-client-examples/functionsyou 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
        }
      }
    }
  `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
        }
      }
    }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.
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}
    }]
  )
}Once executed you should see a result like this
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}
  }
}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.
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
    }
  }
}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.






















