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.
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 →