Skip to content

Recipe: GraphQL Request/Response Capture

Problem

GraphQL requests are POST requests with JSON bodies containing query, variables, and operationName. Standard loggers only show the POST URL, not the actual GraphQL operation.

Solution

http-debugger captures the full request body (including GraphQL query) and response body. The dashboard shows parsed JSON so you can inspect the operation name, variables, and response data.

Code

Apollo Server (Express)

ts
import express from 'express'
import { ApolloServer } from '@apollo/server'
import { expressMiddleware } from '@apollo/server/express4'
import { httpDebugger } from 'http-debugger/express'

const app = express()

// httpDebugger MUST come before express.json() for body capture
app.use(httpDebugger({ maxBodySize: 4096 }))
app.use(express.json())

const server = new ApolloServer({
  typeDefs: `
    type Query {
      user(id: ID!): User
      users: [User!]!
    }
    type User {
      id: ID!
      name: String!
      email: String!
    }
  `,
  resolvers: {
    Query: {
      user: (_, { id }) => ({ id, name: 'Alice', email: 'alice@example.com' }),
      users: () => [{ id: '1', name: 'Alice', email: 'alice@example.com' }]
    }
  }
})

await server.start()
app.use('/graphql', expressMiddleware(server))

app.listen(3000, () => console.log('Server ready at http://localhost:3000/graphql'))

GraphQL Yoga (Hono)

ts
import { Hono } from 'hono'
import { createYoga } from 'graphql-yoga'
import { httpDebugger } from 'http-debugger/hono'

const yoga = createYoga({
  schema: `
    type Query {
      user(id: ID!): User
    }
    type User {
      id: ID!
      name: String!
      email: String!
    }
  `,
  resolvers: {
    Query: {
      user: (_, { id }) => ({ id, name: 'Alice', email: 'alice@example.com' })
    }
  }
})

const app = new Hono()
app.use('*', httpDebugger({ maxBodySize: 4096 }))
app.all('/graphql', (c) => yoga.fetch(c.req.raw))
app.get('/graphql', (c) => yoga.fetch(c.req.raw))

export default app

Next.js App Router

ts
// app/api/graphql/route.ts
import { withHttpDebugger } from 'http-debugger/next'
import { createYoga } from 'graphql-yoga'

const yoga = createYoga({
  schema: `
    type Query {
      user(id: ID!): User
    }
    type User {
      id: ID!
      name: String!
      email: String!
    }
  `,
  resolvers: {
    Query: {
      user: (_, { id }) => ({ id, name: 'Alice', email: 'alice@example.com' })
    }
  }
})

export const GET = withHttpDebugger(yoga.fetch, { maxBodySize: 4096 })
export const POST = withHttpDebugger(yoga.fetch, { maxBodySize: 4096 })

Dashboard Result

The dashboard shows:

Request:

json
{
  "query": "query GetUser($id: ID!) { user(id: $id) { id name email } }",
  "variables": { "id": "1" },
  "operationName": "GetUser"
}

Response:

json
{
  "data": {
    "user": { "id": "1", "name": "Alice", "email": "alice@example.com" }
  }
}

Timing breakdown:

  • Headers: 0.5ms
  • Body Read: 1.2ms (GraphQL parsing)
  • Handler: 8.3ms (resolver execution)
  • Response: 2.1ms (serialization)

Generated cURL

bash
curl -X POST 'http://localhost:3000/graphql' \
  -H 'content-type: application/json' \
  -d '{"query":"query GetUser($id: ID!) { user(id: $id) { id name email } }","variables":{"id":"1"},"operationName":"GetUser"}'

Tips

  1. Increase maxBodySize — GraphQL queries with variables can exceed 1KB default
  2. Filter health checks — Exclude introspection queries in development:
    ts
    httpDebugger({
      filter: (entry) => !entry.request.body?.query?.includes('__schema')
    })
  3. Batch queries — Each batched operation appears as a single entry with multiple operations in the body
  4. Persisted queries — If using Automatic Persisted Queries (APQ), the request body contains extensions.persistedQuery.sha256Hash — http-debugger captures this automatically

Released under the MIT License with No-Resale Clause.