GraphQL Helix HTTP Server Utilities
GraphQL Helix is a collection of framework and runtime agnostic utility functions designed for building GraphQL HTTP servers. It focuses on adherence to the GraphQL over HTTP specification, enabling a single HTTP endpoint for queries, mutations, subscriptions, and features like `@defer` and `@stream` directives. The package supports both server push and client pull paradigms for real-time data. It is known for its minimal footprint, having zero dependencies outside of `graphql-js` itself, and works across Node.js, Deno, and browser environments. The current stable version is 1.13.0, with minor and patch releases occurring frequently, as indicated by recent changes adding `extensions` and `operationName` to `ExecutionContext` and improving `accept` header handling. Its key differentiators include its agnosticism to specific HTTP frameworks and runtimes, strong HTTP-first and spec-compliant approach, and a focus on providing core abstractions without bloat or integrated platforms.
Common errors
-
Error [ERR_REQUIRE_ESM]: require() of ES Module .../node_modules/graphql-helix/lib/index.js from .../server.js not supported.
cause Attempting to use `require()` to import `graphql-helix` in a CommonJS module, but `graphql-helix` is an ES Module.fixConvert your consuming file to an ES Module by using `import ... from 'graphql-helix';` and ensure your `package.json` specifies `"type": "module"` or the file has a `.mjs` extension. -
TypeError: Cannot read properties of undefined (reading 'headers') at getGraphQLParameters
cause The `request` object passed to `getGraphQLParameters` or `processRequest` is missing the `headers` property or is `undefined`.fixEnsure the `request` object you construct for `graphql-helix` contains `headers`, `method`, `query`, and `body` properties, even if some are empty objects or strings for simpler requests. -
Error: GraphQL.js cannot execute a request, because the provided schema is not a valid GraphQLSchema instance.
cause `processRequest` received an invalid `schema` object; it must be a valid `GraphQLSchema` instance from `graphql-js`.fixEnsure your schema is correctly built using `buildSchema` or `makeExecutableSchema` (if using `@graphql-tools`) and passed as the `schema` option to `processRequest`. -
RangeError: Maximum call stack size exceeded (on multipart/mixed responses or SSE)
cause Improper handling of asynchronous iterators for `@defer`/`@stream` or subscriptions, or not closing the HTTP connection properly after sending all parts.fixEnsure that your HTTP server implementation correctly awaits the `result.subscribe()` iterator for `MULTIPART_RESPONSE` or `PUSH` types, and properly terminates the response (e.g., `res.end()` for multipart or ensuring event stream ends).
Warnings
- breaking GraphQL Helix moved to supporting only ESM (ECMAScript Modules) in its newer versions. While `require` might work in some transpiled environments, native ESM imports (`import`) are the intended and fully supported way to consume the library.
- gotcha The `graphql` package is a peer dependency. Installing `graphql-helix` without a compatible version of `graphql` will lead to runtime errors or module resolution issues.
- gotcha Since `graphql-helix@1.11.0`, clients accepting `application/graphql+json` (via the `Accept` header) will receive responses with `Content-Type: application/graphql+json` instead of `application/json` for spec compliance. Clients not specifying an `Accept` header still receive `application/json`.
- gotcha Prior to `graphql-helix@1.10.1`, errors thrown within `subscribe` handlers for `Subscription` root types were not properly handled and could expose internal error messages to clients.
- gotcha Issues with context passing in `processRequest` (specifically related to `contextValue`) were patched in `graphql-helix@1.9.1`, potentially leading to `undefined` or incorrect context in resolvers for earlier versions.
Install
-
npm install graphql-helix -
yarn add graphql-helix -
pnpm add graphql-helix
Imports
- processRequest
const { processRequest } = require('graphql-helix');import { processRequest } from 'graphql-helix'; - getGraphQLParameters
import getGraphQLParameters from 'graphql-helix';
import { getGraphQLParameters } from 'graphql-helix'; - renderGraphiQL
import * as helix from 'graphql-helix'; helix.renderGraphiQL();
import { renderGraphiQL } from 'graphql-helix';
Quickstart
import express from 'express';
import { buildSchema } from 'graphql';
import {
getGraphQLParameters,
processRequest,
renderGraphiQL,
shouldRenderGraphiQL,
} from 'graphql-helix';
const schema = buildSchema(`
type Query {
hello: String
}
type Subscription {
greeting: String
}
`);
const rootValue = {
hello: () => 'Hello GraphQL Helix!',
greeting: async function* () {
for (const hi of ['Hi', 'Bonjour', 'Hola', 'Ciao']) {
yield { greeting: hi };
await new Promise(resolve => setTimeout(resolve, 500));
}
},
};
const app = express();
app.use(express.json());
app.use('/graphql', async (req, res) => {
const request = {
body: req.body,
headers: req.headers,
method: req.method,
query: req.query,
};
if (shouldRenderGraphiQL(request)) {
res.send(renderGraphiQL());
} else {
const { operationName, query, variables } = getGraphQLParameters(request);
const result = await processRequest({
operationName,
query,
variables,
request,
schema,
contextFactory: () => ({ request }),
rootValue,
});
if (result.type === 'RESPONSE') {
result.headers.forEach(({ name, value }) => res.setHeader(name, value));
res.status(result.status);
res.json(result.payload);
} else if (result.type === 'MULTIPART_RESPONSE') {
res.writeHead(200, {
Connection: 'keep-alive',
'Content-Type': 'multipart/mixed; boundary="-"',
'Transfer-Encoding': 'chunked',
});
req.on('close', () => {
result.unsubscribe();
});
res.write('---');
for await (const chunk of result.subscribe()) {
res.write(`\r\n${JSON.stringify(chunk)}\r\n---`);
}
res.end();
} else if (result.type === 'PUSH') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
Connection: 'keep-alive',
'Cache-Control': 'no-cache',
});
req.on('close', () => {
result.unsubscribe();
});
for await (const event of result.subscribe()) {
res.write(`data: ${JSON.stringify(event)}\n\n`);
}
}
}
});
app.listen(4000, () => console.log('GraphQL server running on http://localhost:4000/graphql'));