Overview

Build a GraphQL API with Mercurius, Fastify's official GraphQL adapter. Supports subscriptions out of the box.

Features

  • Mercurius integration
  • GraphQL subscriptions support
  • GraphiQL playground in development
  • Context for authentication
  • Error handler integration

Installation

npx hanma add fastify-graphql-server --framework fastify

Dependencies

fastify, mercurius, graphql

Code

import Fastify, { FastifyInstance } from "fastify";
import mercurius from "mercurius";

// Type definitions
const schema = `
  type Query {
    hello(name: String): String!
    health: HealthStatus!
  }

  type Mutation {
    echo(message: String!): EchoResponse!
  }

  type Subscription {
    countdown(from: Int!): Int!
  }

  type HealthStatus {
    status: String!
    timestamp: String!
    uptime: Float!
  }

  type EchoResponse {
    message: String!
    timestamp: String!
  }
`;

// Resolvers
const resolvers = {
  Query: {
    hello: (_: unknown, { name }: { name?: string }) => {
      return `Hello, ${name ?? "World"}!`;
    },
    health: () => ({
      status: "healthy",
      timestamp: new Date().toISOString(),
      uptime: process.uptime(),
    }),
  },
  Mutation: {
    echo: (_: unknown, { message }: { message: string }) => ({
      message,
      timestamp: new Date().toISOString(),
    }),
  },
  Subscription: {
    countdown: {
      subscribe: async function* (_: unknown, { from }: { from: number }) {
        for (let i = from; i >= 0; i--) {
          await new Promise((resolve) => setTimeout(resolve, 1000));
          yield { countdown: i };
        }
      },
    },
  },
};

// Context type for authentication
interface GraphQLContext {
  user?: {
    id: string;
    email: string;
    roles: string[];
  };
}

export async function createGraphQLServer(): Promise<FastifyInstance> {
  const app = Fastify({
    logger: {
      level: process.env.LOG_LEVEL ?? "info",
    },
  });

  await app.register(mercurius, {
    schema,
    resolvers,
    subscription: true,
    graphiql: process.env.NODE_ENV !== "production",
    context: (request): GraphQLContext => {
      // Extract user from JWT or session here
      // const token = request.headers.authorization?.replace("Bearer ", "");
      return {
        // user: decoded user from token
      };
    },
    errorHandler: (error, request, reply) => {
      app.log.error({ error, path: request.url }, "GraphQL error");
      
      // Don't expose internal errors in production
      if (process.env.NODE_ENV === "production") {
        reply.send({
          errors: [{ message: "An error occurred" }],
        });
      } else {
        reply.send({
          errors: [{ message: error.message, stack: error.stack }],
        });
      }
    },
  });

  // Health check
  app.get("/health", async () => ({
    status: "healthy",
    graphql: "/graphql",
    graphiql: process.env.NODE_ENV !== "production" ? "/graphiql" : "disabled",
  }));

  return app;
}

// Usage:
// const app = await createGraphQLServer();
// await app.listen({ port: 4000 });
// GraphQL playground available at http://localhost:4000/graphiql

Usage

import { createGraphQLServer } from './graphql-server';

const app = await createGraphQLServer();
await app.listen({ port: 4000 });

Dependencies

Frequently Asked Questions

How do I install GraphQL Server (Mercurius)?
Run the following command: npx hanma add fastify-graphql-server --framework fastify
What dependencies are required?
This snippet requires: fastify, mercurius, graphql. Make sure to install them before using the snippet.
How do I use this snippet?
Copy the code and paste it into your project. The snippet will be saved to src/graphql-server.ts.
Can I customize this snippet?
Yes! All snippets are copy-paste code that you own. Feel free to modify them to fit your needs.
Does this work with other frameworks?
This snippet is designed for the specific framework. Check the other framework pages for equivalent implementations.