Error Handling

QuickDapp uses a structured error system with typed error classes on the server and consistent error codes in the GraphQL API. In production, error details are masked to prevent information leakage. Sentry captures errors automatically when configured.

Error Classes

All custom errors extend ApplicationError, which adds a code and optional metadata to the standard Error. These classes are defined in src/server/lib/errors.ts:

ClassGraphQL CodeWhen to Use
ValidationErrorINVALID_INPUTUser input fails validation
AuthenticationErrorAUTHENTICATION_FAILEDCredentials are incorrect
DatabaseErrorDATABASE_ERRORDatabase operation fails
NotFoundErrorNOT_FOUNDRequested resource doesn't exist
ExternalServiceErrorINTERNAL_ERRORThird-party API call fails
AccountDisabledErrorACCOUNT_DISABLEDDisabled user tries to access protected operation

Usage in resolvers:

import { ValidationError, NotFoundError } from "../lib/errors"

if (!email) {
  throw new ValidationError("Email is required")
}

const user = await findUser(db, userId)
if (!user) {
  throw new NotFoundError("User not found", { userId })
}

GraphQL Error Codes

Error codes are defined in src/shared/graphql/errors.ts and available on both client and server:

enum GraphQLErrorCode {
  UNAUTHORIZED = "UNAUTHORIZED",
  AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED",
  INVALID_SIGNATURE = "INVALID_SIGNATURE",
  ACCOUNT_DISABLED = "ACCOUNT_DISABLED",
  OAUTH_CONFIG_ERROR = "OAUTH_CONFIG_ERROR",
  OAUTH_PROVIDER_ERROR = "OAUTH_PROVIDER_ERROR",
  OAUTH_STATE_INVALID = "OAUTH_STATE_INVALID",
  DATABASE_ERROR = "DATABASE_ERROR",
  NOT_FOUND = "NOT_FOUND",
  INVALID_INPUT = "INVALID_INPUT",
  INTERNAL_ERROR = "INTERNAL_ERROR",
}

The client receives these in error.response.errors[0].extensions.code, making it straightforward to handle specific error types in the frontend.

Error Masking

In development, full error messages and stack traces are returned in GraphQL responses for debugging. In production, errors from ApplicationError subclasses return their message and code, while unexpected errors are masked to prevent leaking implementation details.

Sentry Integration

When SENTRY_DSN is configured, errors are automatically captured by Sentry with:

  • User context — The authenticated user ID is attached to error reports
  • Performance tracing — Database operations and resolver execution are traced via startSpan()
  • Worker errors — Worker processes have their own Sentry DSN (SENTRY_WORKER_DSN) for separate error tracking

Configuration:

SENTRY_DSN=https://[email protected]/...
SENTRY_WORKER_DSN=https://[email protected]/...
SENTRY_TRACES_SAMPLE_RATE=1.0
SENTRY_PROFILE_SESSION_SAMPLE_RATE=1.0

Adding Custom Error Types

Extend ApplicationError with a GraphQL error code:

import { GraphQLErrorCode } from "../../shared/graphql/errors"
import { ApplicationError } from "./errors"

export class RateLimitError extends ApplicationError {
  constructor(message = "Too many requests", metadata?: Record<string, any>) {
    super(message, GraphQLErrorCode.INTERNAL_ERROR, metadata)
  }
}

To add a new error code, add it to the GraphQLErrorCode enum in src/shared/graphql/errors.ts — this makes it available to both client and server code.

See src/server/lib/errors.ts for the error class implementations and src/shared/graphql/errors.ts for the error code enum.