> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deploybaseline.com/llms.txt
> Use this file to discover all available pages before exploring further.

# GraphQL

> Serve a GraphQL API from a custom emulator with @deploybaseline/graphql

`@deploybaseline/graphql` is the GraphQL kit the Linear and GitHub emulators run on. Use it to serve a GraphQL API from a [custom emulator](/custom-emulators.md): it reads requests, runs them against your schema, and gives you Relay connections, global IDs, and error helpers. It uses [`graphql`](https://www.npmjs.com/package/graphql) (graphql-js), so queries are validated against your schema exactly as a real server would.

```bash theme={null}
npm install -D @deploybaseline/cli @deploybaseline/graphql
```

## Serve an endpoint

```typescript theme={null}
import { defineEmulator } from '@deploybaseline/cli'
import { buildSchema, connectionFromArray, graphqlEndpoint, graphqlError } from '@deploybaseline/graphql'

const schema = buildSchema(`
  type Query { books(first: Int, after: String): BookConnection!, book(id: ID!): Book }
  type Mutation { addBook(title: String!): Book! }
  type Book { id: ID!, title: String! }
  type BookEdge { node: Book!, cursor: String! }
  type PageInfo { hasNextPage: Boolean!, hasPreviousPage: Boolean!, startCursor: String, endCursor: String }
  type BookConnection { nodes: [Book!]!, edges: [BookEdge!]!, pageInfo: PageInfo! }
`)

export default defineEmulator({
  name: 'library',
  state: () => ({ books: [] as Array<{ id: string; title: string }> }),
  setup({ app, state }) {
    graphqlEndpoint(app, {
      schema,
      context: (c) => ({ state, token: c.req.header('authorization') }),
      root: ({ state }) => ({
        books: (args) => connectionFromArray(state.books, args),
        book: ({ id }) => {
          const book = state.books.find((b) => b.id === id)
          if (!book) throw graphqlError(`Book ${id} not found`, { code: 'NOT_FOUND' })
          return book
        },
        addBook: ({ title }) => {
          const book = { id: String(state.books.length + 1), title }
          state.books.push(book)
          return book
        },
      }),
    })
  },
})
```

`root(context)` returns the resolvers for the root fields of `Query` and `Mutation` together, keyed by field name. Fields of other types resolve from the objects you return, as in graphql-js. Build the schema with the `buildSchema` this package exports: graphql-js refuses a schema built by a different copy of the library.

| Option | Default | |
| - | - | - |
| `schema` | required | A `GraphQLSchema`, or a function that builds it on the first request (a large vendored schema then costs nothing at startup) |
| `context(c)` | required | The per-request value passed to resolvers |
| `root(context)` | required | Root field resolvers |
| `path` | `/graphql` | Where the endpoint is served |
| `get` | `true` | Also answer GET requests (queries only) |
| `status(outcome)` | see below | The HTTP status for a response |
| `extensions(outcome, context)` | none | Extra top-level `extensions`, such as a rate limit or query cost |
| `formatError(error, outcome)` | graphql-js shape | Shape each error, such as GitHub's top-level `type` |
| `strictFields` | `false` | Fail any field the resolved object lacks as not emulated, not just root fields |
| `missingField(info, source)` | none | Answer a field the resolved object lacks; return `undefined` to fall through to `strictFields` or `null` |
| `operations` | every root field | The root fields to declare for the coverage matrix |

## Requests and responses

* **POST** takes a JSON body (`{ query, variables, operationName }`), a form body with the same fields, or an `application/graphql` body holding the query.
* **GET** takes `query`, `variables` (JSON), and `operationName` as query parameters, and runs queries only. A mutation over GET gets HTTP 405 with `Allow: POST`.
* **Status.** By default a request that ran answers 200, even when resolvers returned errors, and a request that could not run answers 400: an empty query, a syntax or validation error, an unknown operation name, or a malformed body. Pass `status` to follow the vendor; Linear answers any error with 400.
* **Not emulated.** A root field that is in the schema but has no resolver answers with the error `Query.<field> is not emulated by baseline` (`extensions.code` is `NOT_EMULATED`), so a client hits a clear gap instead of a confusing `null`. This lets you start from the vendor's full schema and add resolvers as you need them. With `strictFields`, the same goes for every field: a property your object does not have fails as `<Type>.<field> is not emulated by baseline` instead of reading as `null`, which matters when the schema is much larger than what you serve. `missingField` sits in front of that check: Linear uses it to answer unmodeled fields with their type's empty value (`null`, `false`, `0`, an empty list or connection) and to fail the rest as not emulated, because `@linear/sdk` selects every scalar of a model.
* **Errors** thrown in a resolver become GraphQL errors with `path` and `locations`. `graphqlError(message, extensions)` adds `extensions`, such as `{ code: 'FORBIDDEN' }`.

## Helpers

| Helper | |
| - | - |
| `connectionFromArray(items, args, { cursorPrefix, defaultSize })` | A Relay connection (`nodes`, `edges`, `pageInfo`) from a list, with `first`/`after` and `last`/`before`. Cursors are base64url of `<cursorPrefix>:<index>`, so they can look like the vendor's. Default page size 50 |
| `encodeCursor(index, prefix)`, `decodeCursor(cursor)` | The cursors `connectionFromArray` uses. `decodeCursor` returns -1 for a cursor it did not make |
| `toGlobalId(type, id)`, `fromGlobalId(id)` | Relay global node IDs: base64 of `<type>:<id>` |
| `graphqlError(message, extensions?)`, `notEmulatedError(field)` | Errors with `extensions` |
| `readGraphQLRequest(c)`, `executeGraphQL(options)` | The two halves of `graphqlEndpoint`, for an endpoint with its own routing |
| `rootFields(schema)` | Every root field as `Query.<name>` or `Mutation.<name>` |

## Test coverage

`graphqlEndpoint` declares every root field in the schema (or the `operations` you list), and records each root field a test resolves. Provider packages list them in their coverage matrix next to routes, so the coverage gates treat `Query.issues` like `GET /issues`:

```typescript theme={null}
export const coverage: CoverageEntry[] = [
  { route: 'POST /graphql', status: 'supported' },
  { route: 'Query.issues', status: 'supported' },
  { route: 'Mutation.issueCreate', status: 'partial', notes: 'No cycle validation' },
  { route: 'Query.customers', status: 'not_started' },
]
```

A root field without a resolver may stay `not_started`; it answers "not emulated". A `supported` one that no test reaches fails the runtime gate. `pnpm conformance` counts root fields apart from routes.

## Where it runs

The kit is plain HTTP, so a custom emulator with GraphQL works in the CLI and in `createEmulator`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.