Skip to content

graphql/graphql-http

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

56 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

graphql-http

Simple, pluggable, zero-dependency, GraphQL over HTTP Protocol compliant server and client.

Continuous integration graphql-http

Need subscriptions? Try graphql-ws or graphql-sse instead!


Getting started

Install

yarn add graphql-http

Create a GraphQL schema

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
import http from 'http'; import { createHandler } from 'graphql-http'; import { schema } from './previous-step'; // Create the GraphQL over HTTP handler const handler = createHandler({ schema }); // Create a HTTP server using the handler on `/graphql` const server = http.createServer(async (req, res) => { if (!req.url.startsWith('/graphql')) { return res.writeHead(404).end(); } try { const [body, init] = await handler({ url: req.url, method: req.method, headers: req.headers, body: await 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');
With http2

Browsers might complain about self-signed SSL/TLS certificates. Help can be found on StackOverflow.

$ openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \ -keyout localhost-privkey.pem -out localhost-cert.pem
import fs from 'fs'; import http2 from 'http2'; import { createHandler } from 'graphql-http'; import { schema } from './previous-step'; // Create the GraphQL over HTTP 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'), }, async (req, res) => { if (!req.url.startsWith('/graphql')) { return res.writeHead(404).end(); } try { const [body, init] = await handler({ url: req.url, method: req.method, headers: req.headers, body: await 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');
With express
import express from 'express'; // yarn add express import { createHandler } from 'graphql-http'; import { schema } from './previous-step'; // Create the GraphQL over HTTP handler const handler = createHandler({ schema }); // Create an express app serving all methods on `/graphql` const app = express(); app.use('/graphql', async (req, res) => { try { const [body, init] = await handler({ url: req.url, method: req.method, headers: req.headers, body: await 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); } }); app.listen(4000); console.log('Listening to port 4000');
With fastify
import Fastify from 'fastify'; // yarn add fastify import { createHandler } from 'graphql-http'; import { schema } from './previous-step'; // Create the GraphQL over HTTP handler const handler = createHandler({ schema }); // Create a fastify instance serving all methods on `/graphql` const fastify = Fastify(); fastify.all('/graphql', async (req, res) => { try { const [body, init] = await handler({ url: req.url, method: req.method, headers: req.headers, body: req.body, // fastify reads the body for you raw: req, }); res.writeHead(init.status, init.statusText, init.headers).end(body); } catch (err) { res.writeHead(500).end(err.message); } }); fastify.listen(4000); console.log('Listening to port 4000');
With Deno
import { serve } from 'https://deno.land/std@0.151.0/http/server.ts'; import { createHandler } from 'https://esm.sh/graphql-http'; import { schema } from './previous-step'; // Create the GraphQL over HTTP handler const handler = createHandler<Request>({ schema }); // Start serving on `/graphql` using the handler await serve( async (req: Request) => { const [path, _search] = req.url.split('?'); if (!path.endsWith('/graphql')) { return new Response(null, { status: 404, statusText: 'Not Found' }); } const headers: Record<string, string> = {}; req.headers.forEach((value, key) => (headers[key] = value)); const [body, init] = await handler({ url: req.url, method: req.method, headers, body: await req.text(), raw: req, }); return new Response(body, init); }, { port: 4000, }, ); // Listening to port 4000

Use the client

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' }); })();

Recipes

πŸ”— Client usage with Promise
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<Data, Extensions>( params: RequestParams, ): [request: Promise<ExecutionResult<Data, Extensions>>, cancel: () => void] { let cancel!: () => void; const request = new Promise<ExecutionResult<Data, Extensions>>( (resolve, reject) => { let result: ExecutionResult<Data, Extensions>; cancel = client.subscribe<Data, Extensions>(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
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
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
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<FetchResult> { return new Observable((sink) => { return this.client.subscribe<FetchResult>( { ...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
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
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>GraphQL over HTTP</title> <script type="text/javascript" src="https://unpkg.com/graphql-http/umd/graphql-http.min.js" ></script> </head> <body> <script type="text/javascript"> const client = graphqlHttp.createClient({ url: 'http://umdfor.the:4000/win/graphql', }); // consider other recipes for usage inspiration </script> </body> </html>
πŸ”— Client usage in Node
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
import { createClient } from 'graphql-http'; const client = createClient({ url: 'http://deno.earth:4000/graphql', }); // consider other recipes for usage inspiration
πŸ”— 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) because some GraphQL preparations or operations are executed even if the user is not unauthorized.

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
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 direcly });
πŸ”— Server handler usage with custom execution arguments
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.

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: await 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');

Check the docs folder out for TypeDoc generated documentation.

File a bug, contribute with code, or improve documentation? Read up on our guidelines for contributing and drive development with yarn test --watch away!