End to End React with Prisma 2
Start at the beginning and work your way through this project. The code for each step as well as the finished project can be found in the Github repository.
5. Add Context and Middleware
Objective: Add the user and prisma to the context so we can access it in all of our resolvers.
The context
is a graphQL object that allows us to load useful bits of data into it for use into the resolvers. We will use it to add the prisma object which has all of the methods that we will use across our application.
We call the PrismaClient
and instantiate a new prisma client connection using the variable prisma
. During development we need to make sure that we use the same connection even when the Next.js server hot-reloads itself as we change files. We can account for these reloads by attaching prisma to a globalThis
parameter. Without this fix, you will encounter a problem after a number of reloads where you saturate your db connections- not fun for anyone. Many thanks to blitz-js
for finding a good solution to this and Daniel Norman from Prisma for identifying this solution!
After defining prisma
we can define the prisma object. There are two parameters that we want to pass in our context, the prisma
object we mentioned earlier, and a user
object. We will use getSession
to fetch the user login information from the auth0 session. We then will lookup whether a user already exists in the database. If it does we return that as a user object in the context. If prisma cannot find a user to match the auth0 session token, it will throw an error that we catch and in that case we return an empty user object.
The result of all of this is that we always return the prisma object and also either return the user in the context or an empty object.
Let's install uuid, which will allow us to generate unique ids for our new items. Note that there is a specific reason why we are generating ids ourselves rather than letting prisma do it for us. This is because we want to generate the ids on the client side so that we can utilize optimistic updates and be sure of what the id parameter will be. Let's install uuid:
npm install --save uuid
utils/api/context.ts
ts
import auth0 from './auth0'import { PrismaClient } from '@prisma/client'import { v4 as uuidv4 } from 'uuid'let prisma: PrismaClientif (process.env.NODE_ENV === 'production') {prisma = new PrismaClient()} else {globalThis['prisma'] = globalThis['prisma'] || new PrismaClient()prisma = globalThis['prisma']}export const context = async ({ req }) => {try {const { user: auth0User } = await auth0.getSession(req)let user = await prisma.user.findOne({ where: { auth0: auth0User.sub } })if (!user) {const { picture, nickname, sub } = auth0Useruser = await prisma.user.create({data: { id: uuidv4(), auth0: sub, nickname, picture },})}return { user, prisma }} catch (e) {return { user: {}, prisma }}}
Now we can pass it into our Apollo server like this:
pages/api/graphql.ts
ts
// more code aboveconst handler = new ApolloServer({schema,context,}).createHandler({path: '/api/graphql',})//more code below
Let's also add graphql-shield and graphql-middleware now that we have a way to test for whether the user is present or not. We'll also throw in lodash for good measure because it makes checking for whether the user object is empty or not a piece of cake.
npm install --save graphql-shield lodash graphql-middleware
Now let's add graphql permissions using the graphql-shield
package that we already added. Modify the graphql.ts file so that it looks like this:
pages/api/graphql.ts
ts
import { ApolloServer } from 'apollo-server-micro'import Cors from 'micro-cors'import { makeExecutableSchema } from 'graphql-tools'import { context } from '../../utils/api/context'import { typeDefs } from '../../utils/api/typeDefs'import { resolvers } from '../../utils/api/resolvers'import { applyMiddleware } from 'graphql-middleware'import { permissions } from '../../utils/api/permissions'import { log } from '../../utils/api/log'const cors = Cors()const schema = applyMiddleware(makeExecutableSchema({ typeDefs, resolvers }),permissions,log)export const config = {api: {bodyParser: false,},}const handler = new ApolloServer({schema,context,}).createHandler({path: '/api/graphql',})export default cors((req, res) => {if (req.method === 'OPTIONS') {return res.status(200).send()} else {return handler(req, res)}})
You can see that we added an applyMiddleware
method around the makeExecutableSchema
call. GraphQL middleware is a way that we can add additional functions that get executed when a request comes in. Let's define the permissions
middleware first. Create a new file:
utils/api/permissions.ts
ts
import { rule, shield } from 'graphql-shield'import * as _ from 'lodash'const rules = {isAuthenticated: rule()(async (_parent, _args, context) => {return _.isEmpty(context.user) ? false : true}),}export const permissions = shield({Query: {// hello: rules.isAuthenticated,},// Mutation: {},})
Finally let's add the log middleware. This will ensure that we log to the terminal any errors that happen so we can tell where we went wrong!
utils/api/log.ts
ts
export const log = async (resolve, parent, args, ctx, info) => {try {const res = await resolve()return res} catch (e) {console.log(e)}}
Go back to http://localhost:3000/api/graphql
and run the hello
query. Now go back to the permissions.ts
file and uncomment the hello: rules.isAuthenticated
line. Run the query again, you should get an unauthorized error. Finally comment the hello: rules.isAuthenticated
back out so we can access it again.