Apollo Server Errors for Clients with TypeScript

June 3, 2022

2 min read

Apollo Server Errors for Clients with TypeScript
Watch on YouTube

Here I have a resolver for coupon code redemption. It should throw an error when the code doesn't exist or has a claim. Apollo server provides a few errors we can use to communicate errors to a client.

import { OperationContext } from "../../../graphql/OperationContext"
import { assertUserId } from "../../../auth/assertUserId"
import * as appSumoCodesDb from "../db"
import { UserInputError } from "apollo-server-lambda"

interface Input {
  code: string
}

export const redeemAppSumoCode = async (
  _: any,
  { input: { code } }: { input: Input },
  context: OperationContext
) => {
  const userId = assertUserId(context)

  const appSumoCode = await appSumoCodesDb.getAppSumoCodeById(code)
  if (!appSumoCode || appSumoCode.userId !== userId) {
    throw new UserInputError("Invalid code")
  }

  // ...
}

We can go to the source code and see that they all extend ApolloErro. So if we need a custom error, we can copy one of the classes from here and change the name with a code.

export class UserInputError extends ApolloError {
  constructor(message: string, extensions?: Record<string, any>) {
    super(message, "BAD_USER_INPUT", extensions)

    Object.defineProperty(this, "name", { value: "UserInputError" })
  }
}

Apollo Server always responds with 200, but if API threw an error, we'll receive errors with a response. I formatted the error to include only the message and extensions. If we want to provide extra information, we can add an object as a second parameter to ApolloError, and it will appear in the extensions object. To omit stack trace from extension, set NODE_ENV to production.

const server = new ApolloServer({
  typeDefs,
  resolvers,
  // ...
  formatError: ({ message, extensions }) => {
    return {
      message,
      extensions,
    }
  },
})

I run Apollo Server on AWS Lambda and use Sentry for error monitoring. Don't forget to pass ignoreSentryErrors, otherwise, your lambda will crash if you reach Sentry's quote.

exports.handler = Sentry.AWSLambda.wrapHandler(server.createHandler(), {
  ignoreSentryErrors: true,
})