GraphQL middleware for session and error logging


Hey all,

Now that Prisma just released their excellent blog post about GraphQL Middleware (created by @matic), thought I’d share with the community how I’m logging all queries and mutations, as well as any errors. Errors are logged in a different model, but each error log has a one-to-one relationship to its corresponding session log.


type LogError @model {
  # Core
  id:			ID! @unique
  createdAt:	DateTime!
  updatedAt:	DateTime!
  # Fields
  args:			Json
  error:		String!
  LogSession:	LogSession! @relation(name: "LogErrorSession", onDelete: SET_NULL)

type LogSession @model {
  # Core
  id:			ID! @unique
  createdAt:	DateTime!
  updatedAt:	DateTime!
  # Fields
  args:			Json
  ipAddress:	String
  LogError:		LogError @relation(name: "LogErrorSession", onDelete: CASCADE)
  origin:		String!
  resolver:		String!

index.js (using Prisma)

const logSession = async (resolve, parent, args, ctx, info) => {
  const session = await ctx.db.mutation.createLogSession({
    data: {
      // Any custom header info can be captured using
      // `ctx.request.headers['__CUSTOM_HEADER_INFO__']`
      ipAddress: ctx.request.headers['IPAddress'],
      // Might be good practice to log the `origin`, and if not available,
      // use `referer`
      origin: ctx.request.headers.origin
        ? ctx.request.headers.origin
        : ctx.request.headers.referer,
      // The name of the resolver can be captured using `info.fieldName`
      resolver: info.fieldName
  }, `{ id }`)
  // Important: notice how we’re adding the session ID as a new arg.
  // Later it gets passed into the error logging middleware
  const argsWithSession = { ...args, sessionId: }
  return await resolve(parent, argsWithSession, ctx, info)

// Apply session logger to all query and mutation resolvers individually. I found
// this works cleaner than the approach shared in the Prisma blog post whereby
// you apply the middleware across all resolvers; you can try out that approach
// by replacing `sessionMiddleware` with `logSession` in the `middlewares` option
// below.
const queryResolvers = Object.keys(resolvers.Query).reduce((result, item) => {
  result[item] = logSession
  return result
}, {})

const mutationResolvers = Object.keys(resolvers.Mutation).reduce((result, item) => {
  result[item] = logSession
  return result
}, {})

// Add session logger to middleware
const sessionMiddleware = {
  Query: {
  Mutation: {

const errorMiddleware = async (resolve, root, args, context, info) => {
  // First resolve all resolvers...
  try {
    return await resolve(root, args, context, info)
  // ...and if an error occurs in any of them, log it and tie it to session.
  // It works for both controlled and uncontrolled errors:
  // - controlled errors can be initiated via:
  // throw new Error('This is an error message.')
  // - uncontrolled errors occur if there’s an unintentional error in the code
  catch (err) {
    // Notice how the sessionId from the `logSession` arg is now being used
    // to connect the error node to its corresponding session node
    const sessionId = args.sessionId
    // We want to delete the sessionId from the args since we just need
    // the sessionId to connect the error node to its session node.
    delete args.sessionId
      data: {
        // Args is the object that contains all of the arguments that were
        // used in calling the resolver; collecting the args may help us
        // understand what went wrong.
        // Error message
        error: err,
        // Connect the error node to its session node
        LogSession: {
          connect: {
            id: sessionId
    // Throw error to communicate error to browser.
    throw new Error(err)

const server = new GraphQLServer({
  typeDefs: 'src/schema.graphql',
  // Add middleware here. NOTE: the sequence is important:
  // first the session logging middleware, then the error logging middleware
  middlewares: [sessionMiddleware, errorMiddleware],
  context: req => {
    return {
      db: new Prisma({
        typeDefs: 'src/generated/prisma.graphql',
        endpoint: process.env.PRISMA_ENDPOINT,
        secret: process.env.PRISMA_SECRET,
        debug: true


This is so cool! Have you ever thought of creating a library out of it?


That’s a fantastic idea, @matic! Never done this before… since you’re the expert, how would you feel if we did this together? I could learn from you that way. Would that work?


Let’s do this! Are you already in our Slack group?


This is cool :ok_hand:

I’m also using something similar. Provide a package and set this up as a simple SaaS model :stuck_out_tongue: