GraphQL’s concept spans from data store to client. What happens along the way? Tooling in the GraphQL world has come a long way and spinning up an application is easier than ever. By stepping through each component, you’ll have a better understanding of GraphQL as a whole.
To follow along with this tutorial, clone this fullstack example. It’s a bare-bones app using sessions to authenticate users, a battle tested and secure option for persisting user auth.
Image from https://www.howtographql.com
Looking at the image above from left to right, the first represents our client application built on Apollo (our GraphQL data cache) and React. I’m using Parcel to build the client bundle.
Next, the “GraphQL Server” will be graphql-yoga, a server based on Express which interfaces with Apollo and provides us with a lot of sensible defaults.
Prisma will stand between the server and the database as our data layer, similar to an ORM for writing and accessing data to and from our database. Prisma is open source and supports all of the major databases (the stack of pancake on the right of the image).
Start by installing some global packages.
yarn global add prisma graphql-cli nodemon
Create a .env
file in our project folder, we’ll fill in the DB_URL variable once we deploy Prisma:
APP_SECRET=mysecret123DB_URL=
Next, create a Prisma account then log into the Prisma CLI with prisma login
so you can spin up free dev servers for your projects. Don’t worry, Prisma can be moved to the hardware of your choice at any time and won’t lock you in.
We’re going to generate our user models and deploy them to our test database. Inside of /database
you’ll see we have one model, User
. When we deploy, Prisma will create the bindings based on this schema. Our .graphqlconfig.yml
in the root of our project will explain to Prisma how we want our setup deployed. Now, run:
prisma deploy database
Select “Demo server”, choose whichever region makes sense, enter a name like “fullstack” and set the stage as “dev”. Prisma will generate an endpoint like [https://us1.prisma.sh/_myusername-12345_/fullstack/dev](https://us1.prisma.sh/myusername-2acca9a/my-project/dev.)
, drop it into your .env
.
One last thing, Prisma will comment out your database/prisma.yml
to:
#endpoint: ${env:DB_URL}endpoint: [https://us1.prisma.sh/myusername-12345/fullstack/dev](https://us1.prisma.sh/myusername-2acca9a/my-project/dev.)
...
So switch it back to:
endpoint: ${env:DB_URL}
...
After installing dependencies with yarn install
or npm install
fire it up your local server with npm run dev
and navigate to localhost:1234
. The dev
command will simultaneously run our server and frontend build.
Sign up and Log in! After a user is successfully created, it will kick you to the dashboard.
Once you’re logged in, the protected routes won’t allow authenticated users to the /signup
or /login
routes. Once logged out, you can’t go to /dashboard
.
Try refreshing while logged in. On page load the app will make a request to check user’s authentication state. If authenticated, the page will then fetch the user’s data.
Open /src/index.js
you’ll see the graphql-yoga
server instantiated with GraphQLServer
. It takes a number of arguments:
Typedefs: This is our GraphQL schema. Inside the file you’ll see we’ve listed the queries and mutations that will be handled by our resolvers.
Resolvers: These are the methods that do the work of logging in, fetching our user, etc. Their inputs and return values must match up with what’s defined in our typeDef’s schema.graphql
.
Context: this takes a function that we’ll use to set the database to point to our Prisma set up. Pass in the generated typeDefs from the prisma deploy
as well as the endpoint and secret from our .env
.
As mentioned above, graphql-yoga
is based on Express so we can use common packages like express-session
to handle our sessions.
After we define our sessions implementation, we start the server with CORS settings that allow our frontend on localhost:1234
(via parcel) to get our session cookies.
Our front end is a barebones React application bundled up with Parcel. Starting in client/index.jsx
we set up the ApolloClient
to connect to our server running on port 4000
and assign the client to our ApolloProvider
.
In the Header
component you’ll see we’re calling the GET_AUTH_STATUS
query which checks if there’s a session on the backend. It’ll determine whether to show the login / signup links or logout.
Further down in index.jsx
you’ll see protected routes for our Dashboard
and auth routes. I created a special route component, ProtectedRoute
, which calls GET_AUTH_STATUS
to decide whether to display the corresponding component. Even though we call our auth status query in a few different places, when you open “Network” in developer tools you’ll see that it is only requested once. This is because Apollo Cache handles the data between different components automatically.
The Auth
component handles both our signup and login mutations. On successful login or signup, we need to update our GET_AUTH_STATUS
becauseLOGIN
and SIGNUP
mutations only return the User object. Instead of making another request for auth status, we’ll just update the status manually:
<Mutationmutation={isSignup ? SIGNUP : LOGIN}variables={isSignup ? { email, name, password } : { email, password }}update={(cache) => {cache.writeQuery({query: GET_AUTH_STATUS,data: {isLoggedIn: {status: true,__typename: 'AuthStatus',},},});}}>
Now’s a good time to dive in to GraphQL if you’ve been watching from the sidelines. The GraphQL eco-system has come a long way in the few years it’s been around. Each of the layers in the stack has matured enough that it’s relatively straightforward to build an app from top to bottom.
Also, interest is on the rise. The most recent “State of Javascript” reflects this sentiment and we’ll only see more in the future.
Why not store JWTs in local storage or cookies? Well, these articles can explain better than me, but suffice to say using server side sessions to save sensitive data is safer (and easy).
Again, here’s a link to the example repository.