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 keyspaceintrographql
using theAdd 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.
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
Connect
TAB - Click the
APIs
connection method - Make sure
GraphQL API
is 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-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.
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-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.
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`
.
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 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>
));
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-cli
tool), be aware that the only variables needed by the React client are:ASTRA_DB_APPLICATION_TOKEN
,ASTRA_DB_GRAPHQL_URL
andJAVA_GRAPHQL_ENDPOINT
.
Launch the following command once more:
netlify dev
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 :("
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 theShowsByName
section -
If you take a look at the
getShowsAstra.js
script ingraphql-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
}
}
}
`
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.