GraphQL with Koa

Expose a Type-Safe GraphQL API Through a Single Koa Route

GraphQL with Koa

Mount a GraphQL endpoint in Koa 2 using graphql-http, define a schema with resolvers, and wire it to an @koa/router route for a clean single-endpoint API.

5 min read Level 3/5 #koa#data#graphql
What you'll learn
  • Define a GraphQL schema and resolver map in a Koa application
  • Mount graphql-http as Koa middleware on a single /graphql route
  • Test queries with curl and understand how the context object passes services

GraphQL exposes your data through a single /graphql endpoint where clients specify exactly what fields they need. graphql-http is the spec-compliant, framework-agnostic server library recommended by the GraphQL Foundation.

Installation

npm install graphql graphql-http

Defining the Schema

// schema.js
import { buildSchema } from 'graphql';

export const schema = buildSchema(`
  type User {
    id:    ID!
    name:  String!
    email: String!
  }

  type Query {
    users: [User!]!
    user(id: ID!): User
  }

  type Mutation {
    createUser(name: String!, email: String!): User!
  }
`);

Resolvers

// resolvers.js
export const rootValue = {
  async users(_args, context) {
    const { rows } = await context.db.query('SELECT id, name, email FROM users');
    return rows;
  },

  async user({ id }, context) {
    const { rows } = await context.db.query(
      'SELECT id, name, email FROM users WHERE id = $1',
      [id]
    );
    return rows[0] ?? null;
  },

  async createUser({ name, email }, context) {
    const { rows } = await context.db.query(
      'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email',
      [name, email]
    );
    return rows[0];
  },
};

Mounting graphql-http in Koa

graphql-http ships a createHandler function. Wrap it in a thin Koa middleware to bridge Koa’s ctx with the library’s request/response model.

// app.js
import Koa from 'koa';
import Router from '@koa/router';
import { createHandler } from 'graphql-http/lib/adapters/node';
import { schema }    from './schema.js';
import { rootValue } from './resolvers.js';
import { pool }      from './db.js';

const app    = new Koa();
const router = new Router();

const handler = createHandler({
  schema,
  rootValue,
  context: (req) => ({ db: pool, headers: req.headers }),
});

// graphql-http's node adapter works with the raw IncomingMessage/ServerResponse
router.all('/graphql', async (ctx) => {
  const [body, init] = await handler({
    method:  ctx.method,
    headers: ctx.headers,
    body:    () => ctx.request.rawBody ?? JSON.stringify(ctx.request.body),
    raw:     ctx.req,
  });

  ctx.status = init.status;
  for (const [k, v] of Object.entries(init.headers ?? {})) ctx.set(k, v);
  ctx.body = body;
});

app.use(router.routes()).use(router.allowedMethods());
export default app;

Testing with curl

curl -X POST http://localhost:3000/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query":"{ users { id name email } }"}'

Apollo Server (Alternative)

If you prefer the Apollo ecosystem, @apollo/server ships a Koa integration via @as-integrations/koa. It provides a built-in GraphiQL IDE at /graphql in development, schema introspection, and plugin hooks — at the cost of a larger dependency footprint.

Up Next

Add HTTP caching with ETag, Cache-Control, and Redis to reduce unnecessary database hits across repeated requests.

HTTP Caching in Koa →