graphql-http

Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.
[![Continuous integration](https://github.com/graphql/graphql-http/workflows/Continuous%20integration/badge.svg)](https://github.com/graphql/graphql-http/actions?query=workflow%3A%22Continuous+integration%22) [![graphql-http](https://img.shields.io/npm/v/graphql-http.svg?label=graphql-http&logo=npm)](https://www.npmjs.com/package/graphql-http) Quickly check for compliance? Visit [graphql-http.com](https://graphql-http.com)! Want a full-featured server? See the [servers section](#servers)! Need subscriptions? Try [graphql-ws](https://github.com/enisdenjo/graphql-ws) or [graphql-sse](https://github.com/enisdenjo/graphql-sse) instead!
## Getting started #### Install ```shell yarn add graphql-http ``` #### Create a GraphQL schema ```js import { GraphQLSchema, GraphQLObjectType, GraphQLString } from 'graphql'; /** * Construct a GraphQL schema and define the necessary resolvers. * * type Query { * hello: String * } */ const schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'Query', fields: { hello: { type: GraphQLString, resolve: () => 'world', }, }, }), }); ``` #### Start the server ##### With [`http`](https://nodejs.org/api/http.html) ```js import http from 'http'; import { createHandler } from 'graphql-http/lib/use/http'; import { schema } from './previous-step'; // Create the GraphQL over HTTP Node request handler const handler = createHandler({ schema }); // Create a HTTP server using the listener on `/graphql` const server = http.createServer((req, res) => { if (req.url.startsWith('/graphql')) { handler(req, res); } else { res.writeHead(404).end(); } }); server.listen(4000); console.log('Listening to port 4000'); ``` ##### With [`http2`](https://nodejs.org/api/http2.html) _Browsers might complain about self-signed SSL/TLS certificates. [Help can be found on StackOverflow.](https://stackoverflow.com/questions/7580508/getting-chrome-to-accept-self-signed-localhost-certificate)_ ```shell $ openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \ -keyout localhost-privkey.pem -out localhost-cert.pem ``` ```js import fs from 'fs'; import http2 from 'http2'; import { createHandler } from 'graphql-http/lib/use/http2'; import { schema } from './previous-step'; // Create the GraphQL over HTTP Node request handler const handler = createHandler({ schema }); // Create a HTTP/2 server using the handler on `/graphql` const server = http2.createSecureServer( { key: fs.readFileSync('localhost-privkey.pem'), cert: fs.readFileSync('localhost-cert.pem'), }, (req, res) => { if (req.url.startsWith('/graphql')) { handler(req, res); } else { res.writeHead(404).end(); } }, ); server.listen(4000); console.log('Listening to port 4000'); ``` ##### With [`express`](https://expressjs.com/) ```js import express from 'express'; // yarn add express import { createHandler } from 'graphql-http/lib/use/express'; import { schema } from './previous-step'; // Create an express instance serving all methods on `/graphql` // where the GraphQL over HTTP express request handler is const app = express(); app.all('/graphql', createHandler({ schema })); app.listen({ port: 4000 }); console.log('Listening to port 4000'); ``` ##### With [`fastify`](https://www.fastify.io/) ```js import Fastify from 'fastify'; // yarn add fastify import { createHandler } from 'graphql-http/lib/use/fastify'; import { schema } from './previous-step'; // Create a fastify instance serving all methods on `/graphql` // where the GraphQL over HTTP fastify request handler is const fastify = Fastify(); fastify.all('/graphql', createHandler({ schema })); fastify.listen({ port: 4000 }); console.log('Listening to port 4000'); ``` ##### With [`Koa`](https://koajs.com/) ```js import Koa from 'koa'; // yarn add koa import mount from 'koa-mount'; // yarn add koa-mount import { createHandler } from 'graphql-http/lib/use/koa'; import { schema } from './previous-step'; const app = new Koa(); app.use(mount('/graphql', createHandler({ schema }))); app.listen({ port: 4000 }); console.log('Listening to port 4000'); ``` ##### With [`uWebSockets.js`](https://github.com/uNetworking/uWebSockets.js) ```js import uWS from 'uWebSockets.js'; // yarn add uWebSockets.js@uNetworking/uWebSockets.js# import { createHandler } from 'graphql-http/lib/use/uWebSockets'; import { schema } from './previous-step'; uWS .App() .any('/graphql', createHandler({ schema })) .listen(4000, () => { console.log('Listening to port 4000'); }); ``` ##### With [`Deno`](https://deno.land/) ```ts import { serve } from 'https://deno.land/std@0.151.0/http/server.ts'; import { createHandler } from 'https://esm.sh/graphql-http/lib/use/fetch'; import { schema } from './previous-step'; // Create the GraphQL over HTTP native fetch handler const handler = createHandler({ schema }); // Start serving on `/graphql` using the handler await serve( (req: Request) => { const [path, _search] = req.url.split('?'); if (path.endsWith('/graphql')) { return handler(req); } else { return new Response(null, { status: 404 }); } }, { port: 4000, // Listening to port 4000 }, ); ``` ##### With [`Bun`](https://bun.sh/) ```js import { createHandler } from 'graphql-http/lib/use/fetch'; // bun install graphql-http import { schema } from './previous-step'; // Create the GraphQL over HTTP native fetch handler const handler = createHandler({ schema }); // Start serving on `/graphql` using the handler export default { port: 4000, // Listening to port 4000 fetch(req) { const [path, _search] = req.url.split('?'); if (path.endsWith('/graphql')) { return handler(req); } else { return new Response(null, { status: 404 }); } }, }; ``` ##### With [`Netlify Functions`](https://docs.netlify.com/functions/overview/) ```js import { createHandler } from 'graphql-http/lib/use/@netlify/functions'; // yarn add @netlify/functions import { schema } from './previous-step'; // Create the GraphQL over HTTP native fetch handler export const handler = createHandler({ schema }); ``` #### Use the client ```js import { createClient } from 'graphql-http'; const client = createClient({ url: 'http://localhost:4000/graphql', }); (async () => { let cancel = () => { /* abort the request if it is in-flight */ }; const result = await new Promise((resolve, reject) => { let result; cancel = client.subscribe( { query: '{ hello }', }, { next: (data) => (result = data), error: reject, complete: () => resolve(result), }, ); }); expect(result).toEqual({ hello: 'world' }); })(); ``` #### Serve [GraphiQL](https://github.com/graphql/graphiql) Thanks to [`ruru`](https://github.com/graphile/crystal/tree/main/grafast/ruru), serving GraphiQL is as easy as running: ```sh npx ruru -SP -p 4001 -e http://localhost:4000/graphql ``` Open [http://localhost:4001](http://localhost:4001) in the browser to use it. ## Recipes
🔗 Client usage with Promise ```ts import { ExecutionResult } from 'graphql'; import { createClient, RequestParams } from 'graphql-http'; import { getSession } from './my-auth'; const client = createClient({ url: 'http://hey.there:4000/graphql', headers: async () => { const session = await getSession(); if (session) { return { Authorization: `Bearer ${session.token}`, }; } }, }); function execute( params: RequestParams, ): [request: Promise>, cancel: () => void] { let cancel!: () => void; const request = new Promise>( (resolve, reject) => { let result: ExecutionResult; cancel = client.subscribe(params, { next: (data) => (result = data), error: reject, complete: () => resolve(result), }); }, ); return [request, cancel]; } (async () => { const [request, cancel] = execute({ query: '{ hello }', }); // just an example, not a real function onUserLeavePage(() => { cancel(); }); const result = await request; expect(result).toBe({ data: { hello: 'world' } }); })(); ```
🔗 Client usage with Observable ```js import { Observable } from 'relay-runtime'; // or import { Observable } from '@apollo/client/core'; // or import { Observable } from 'rxjs'; // or import Observable from 'zen-observable'; // or any other lib which implements Observables as per the ECMAScript proposal: https://github.com/tc39/proposal-observable import { createClient } from 'graphql-http'; import { getSession } from './my-auth'; const client = createClient({ url: 'http://graphql.loves:4000/observables', headers: async () => { const session = await getSession(); if (session) { return { Authorization: `Bearer ${session.token}`, }; } }, }); const observable = new Observable((observer) => client.subscribe({ query: '{ hello }' }, observer), ); const subscription = observable.subscribe({ next: (result) => { expect(result).toBe({ data: { hello: 'world' } }); }, }); // unsubscribe will cancel the request if it is pending subscription.unsubscribe(); ```
🔗 Client usage with Relay ```ts import { GraphQLError } from 'graphql'; import { Network, Observable, RequestParameters, Variables, } from 'relay-runtime'; import { createClient } from 'graphql-http'; import { getSession } from './my-auth'; const client = createClient({ url: 'http://i.love:4000/graphql', headers: async () => { const session = await getSession(); if (session) { return { Authorization: `Bearer ${session.token}`, }; } }, }); function fetch(operation: RequestParameters, variables: Variables) { return Observable.create((sink) => { if (!operation.text) { return sink.error(new Error('Operation text cannot be empty')); } return client.subscribe( { operationName: operation.name, query: operation.text, variables, }, sink, ); }); } export const network = Network.create(fetch); ```
🔗 Client usage with Apollo ```ts import { ApolloLink, Operation, FetchResult, Observable, } from '@apollo/client/core'; import { print, GraphQLError } from 'graphql'; import { createClient, ClientOptions, Client } from 'graphql-http'; import { getSession } from './my-auth'; class HTTPLink extends ApolloLink { private client: Client; constructor(options: ClientOptions) { super(); this.client = createClient(options); } public request(operation: Operation): Observable { return new Observable((sink) => { return this.client.subscribe( { ...operation, query: print(operation.query) }, { next: sink.next.bind(sink), complete: sink.complete.bind(sink), error: sink.error.bind(sink), }, ); }); } } const link = new HTTPLink({ url: 'http://where.is:4000/graphql', headers: async () => { const session = await getSession(); if (session) { return { Authorization: `Bearer ${session.token}`, }; } }, }); ```
🔗 Client usage with request retries ```ts import { createClient, NetworkError } from 'graphql-http'; const client = createClient({ url: 'http://unstable.service:4000/graphql', shouldRetry: async (err: NetworkError, retries: number) => { if (retries > 3) { // max 3 retries and then report service down return false; } // try again when service unavailable, could be temporary if (err.response?.status === 503) { // wait one second (you can alternatively time the promise resolution to your preference) await new Promise((resolve) => setTimeout(resolve, 1000)); return true; } // otherwise report error immediately return false; }, }); ```
🔗 Client usage in browser ```html GraphQL over HTTP ```
🔗 Client usage in Node ```js const fetch = require('node-fetch'); // yarn add node-fetch const { AbortController } = require('node-abort-controller'); // (node < v15) yarn add node-abort-controller const { createClient } = require('graphql-http'); const client = createClient({ url: 'http://no.browser:4000/graphql', fetchFn: fetch, abortControllerImpl: AbortController, // node < v15 }); // consider other recipes for usage inspiration ```
🔗 Client usage in Deno ```js import { createClient } from 'https://esm.sh/graphql-http'; const client = createClient({ url: 'http://deno.earth:4000/graphql', }); // consider other recipes for usage inspiration ```
🔗 Client usage in Bun ```js import { createClient } from 'graphql-http'; // bun install graphql-http const client = createClient({ url: 'http://bun.bread:4000/graphql', }); // consider other recipes for usage inspiration ```
🔗 Server handler migration from express-graphql ```diff import express from 'express'; import { schema } from './my-graphql-schema'; -import { graphqlHTTP } from 'express-graphql'; +import { createHandler } from 'graphql-http/lib/use/express'; const app = express(); app.use( '/graphql', - graphqlHTTP({ schema }), + createHandler({ schema }), ); app.listen(4000); ```
🔗 Server handler usage with authentication Authenticate the user within `graphql-http` during GraphQL execution context assembly. This is a approach is less safe compared to early authentication ([see early authentication in Node](#auth-node-early)) because some GraphQL preparations or operations are executed even if the user is not unauthorized. ```js import { createHandler } from 'graphql-http'; import { schema, getUserFromCookies, getUserFromAuthorizationHeader, } from './my-graphql'; const handler = createHandler({ schema, context: async (req) => { // process token, authenticate user and attach it to your graphql context const userId = await getUserFromCookies(req.headers.cookie); // or const userId = await getUserFromAuthorizationHeader( req.headers.authorization, ); // respond with 401 if the user was not authenticated if (!userId) { return [null, { status: 401, statusText: 'Unauthorized' }]; } // otherwise attach the user to the graphql context return { userId }; }, }); ```
🔗 Server handler usage with custom context value ```js import { createHandler } from 'graphql-http'; import { schema, getDynamicContext } from './my-graphql'; const handler = createHandler({ schema, context: async (req, args) => { return getDynamicContext(req, args); }, // or static context by supplying the value directly }); ```
🔗 Server handler usage with custom execution arguments ```js import { parse } from 'graphql'; import { createHandler } from 'graphql-http'; import { getSchemaForRequest, myValidationRules } from './my-graphql'; const handler = createHandler({ onSubscribe: async (req, params) => { const schema = await getSchemaForRequest(req); const args = { schema, operationName: params.operationName, document: parse(params.query), variableValues: params.variables, }; return args; }, }); ```
🔗 Server handler usage in Node with early authentication (recommended) Authenticate the user early, before reaching `graphql-http`. This is the recommended approach because no GraphQL preparations or operations are executed if the user is not authorized. ```js import { createHandler } from 'graphql-http'; import { schema, getUserFromCookies, getUserFromAuthorizationHeader, } from './my-graphql'; const handler = createHandler({ schema, context: async (req) => { // user is authenticated early (see below), simply attach it to the graphql context return { userId: req.raw.userId }; }, }); const server = http.createServer(async (req, res) => { if (!req.url.startsWith('/graphql')) { return res.writeHead(404).end(); } try { // process token, authenticate user and attach it to the request req.userId = await getUserFromCookies(req.headers.cookie); // or req.userId = await getUserFromAuthorizationHeader( req.headers.authorization, ); // respond with 401 if the user was not authenticated if (!req.userId) { return res.writeHead(401, 'Unauthorized').end(); } const [body, init] = await handler({ url: req.url, method: req.method, headers: req.headers, body: () => new Promise((resolve) => { let body = ''; req.on('data', (chunk) => (body += chunk)); req.on('end', () => resolve(body)); }), raw: req, }); res.writeHead(init.status, init.statusText, init.headers).end(body); } catch (err) { res.writeHead(500).end(err.message); } }); server.listen(4000); console.log('Listening to port 4000'); ```
🔗 Server handler usage with graphql-upload and http ```js import http from 'http'; import { createHandler } from 'graphql-http/lib/use/http'; import processRequest from 'graphql-upload/processRequest.mjs'; // yarn add graphql-upload import { schema } from './my-graphql'; const handler = createHandler({ schema, async parseRequestParams(req) { const params = await processRequest(req.raw, req.context.res); if (Array.isArray(params)) { throw new Error('Batching is not supported'); } return { ...params, // variables must be an object as per the GraphQL over HTTP spec variables: Object(params.variables), }; }, }); const server = http.createServer((req, res) => { if (req.url.startsWith('/graphql')) { handler(req, res); } else { res.writeHead(404).end(); } }); server.listen(4000); console.log('Listening to port 4000'); ```
🔗 Server handler usage with graphql-upload and express ```js import express from 'express'; // yarn add express import { createHandler } from 'graphql-http/lib/use/express'; import processRequest from 'graphql-upload/processRequest.mjs'; // yarn add graphql-upload import { schema } from './my-graphql'; const app = express(); app.all( '/graphql', createHandler({ schema, async parseRequestParams(req) { const params = await processRequest(req.raw, req.context.res); if (Array.isArray(params)) { throw new Error('Batching is not supported'); } return { ...params, // variables must be an object as per the GraphQL over HTTP spec variables: Object(params.variables), }; }, }), ); app.listen({ port: 4000 }); console.log('Listening to port 4000'); ```
🔗 Audit for servers usage in Jest environment ```js import { fetch } from '@whatwg-node/fetch'; import { serverAudits } from 'graphql-http'; for (const audit of serverAudits({ url: 'http://localhost:4000/graphql', fetchFn: fetch, })) { test(audit.name, async () => { const result = await audit.fn(); if (result.status === 'error') { throw result.reason; } if (result.status === 'warn') { console.warn(result.reason); // or throw if you want full compliance (warnings are not requirements) } // result.status === 'ok' }); } ```
🔗 Audit for servers usage in Deno environment ```ts import { serverAudits } from 'npm:graphql-http'; for (const audit of serverAudits({ url: 'http://localhost:4000/graphql', fetchFn: fetch, })) { Deno.test(audit.name, async () => { const result = await audit.fn(); if (result.status === 'error') { throw result.reason; } if (result.status === 'warn') { console.warn(result.reason); // or throw if you want full compliance (warnings are not requirements) } // Avoid leaking resources if ('body' in result && result.body instanceof ReadableStream) { await result.body.cancel(); } }); } ``` Put the above contents in a file and run it with `deno test --allow-net`.
## Only [GraphQL over HTTP](https://graphql.github.io/graphql-over-http/) This is the official [GraphQL over HTTP spec](https://graphql.github.io/graphql-over-http/) reference implementation and as such follows the specification strictly without any additional features (like playgrounds or GUIs, file uploads, @stream/@defer directives and subscriptions). Having said this, graphql-http is mostly aimed for library authors and simple server setups, where the requirements are exact to what the aforementioned spec offers. ## [Servers](/implementations) If you want a feature-full server with bleeding edge technologies, you're recommended to use one of the following servers. Their compliance with the [GraphQL over HTTP spec](https://graphql.github.io/graphql-over-http) is checked automatically and updated regularly. | Name | Audit | |------|-------| | [apollo-server](https://www.apollographql.com/docs/apollo-server) | [✅ Compliant](/implementations/apollo-server/README.md) | | [deno](https://deno.com/blog/build-a-graphql-server-with-deno) | [✅ Compliant](/implementations/deno/README.md) | | [graph-client](https://github.com/graphprotocol/graph-client) | [✅ Compliant](/implementations/graph-client/README.md) | | [graphql-helix](https://www.graphql-helix.com) | [✅ Compliant](/implementations/graphql-helix/README.md) | | [graphql-yoga](https://www.the-guild.dev/graphql/yoga-server) | [✅ Compliant](/implementations/graphql-yoga/README.md) | | [hotchocolate](https://chillicream.com/docs/hotchocolate) | [✅ Compliant](/implementations/hotchocolate/README.md) | | [lighthouse](https://lighthouse-php.com) | [✅ Compliant](/implementations/lighthouse/README.md) | | [pioneer](https://pioneer.dexclaimation.com) | [✅ Compliant](/implementations/pioneer/README.md) | | [postgraphile](https://www.graphile.org/postgraphile) | [✅ Compliant](/implementations/postgraphile/README.md) | ## [Documentation](docs/) Check the [docs folder](docs/) out for [TypeDoc](https://typedoc.org) generated documentation. ## [Audits](implementations/) Inspect audits of other implementations in the [implementations folder](implementations). Adding your implementation is very welcome, [see how](CONTRIBUTING.md#adding-implementations)! ## Want to help? File a bug, contribute with code, or improve documentation? [Read more in CONTRIBUTING.md](CONTRIBUTING.md). If your company benefits from GraphQL and you would like to provide essential financial support for the systems and people that power our community, please also consider membership in the [GraphQL Foundation](https://foundation.graphql.org/join).