Getting started
Install
yarn add graphql-http
Create a GraphQL schema
import { GraphQLSchema, GraphQLObjectType, GraphQLString } from 'graphql';
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
hello: {
type: GraphQLString,
resolve: () => 'world',
},
},
}),
});
Start the server
import http from 'http';
import { createHandler } from 'graphql-http/lib/use/http';
import { schema } from './previous-step';
const handler = createHandler({ schema });
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');
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/lib/use/http2';
import { schema } from './previous-step';
const handler = createHandler({ schema });
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');
import express from 'express';
import { createHandler } from 'graphql-http/lib/use/express';
import { schema } from './previous-step';
const app = express();
app.all('/graphql', createHandler({ schema }));
app.listen({ port: 4000 });
console.log('Listening to port 4000');
import Fastify from 'fastify';
import { createHandler } from 'graphql-http/lib/use/fastify';
import { schema } from './previous-step';
const fastify = Fastify();
fastify.all('/graphql', createHandler({ schema }));
fastify.listen({ port: 4000 });
console.log('Listening to port 4000');
import Koa from 'koa';
import mount from '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');
import uWS from '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');
});
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';
const handler = createHandler({ schema });
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,
},
);
import { createHandler } from 'graphql-http/lib/use/fetch';
import { schema } from './previous-step';
const handler = createHandler({ schema });
export default {
port: 4000,
fetch(req) {
const [path, _search] = req.url.split('?');
if (path.endsWith('/graphql')) {
return handler(req);
} else {
return new Response(null, { status: 404 });
}
},
};
Use the client
import { createClient } from 'graphql-http';
const client = createClient({
url: 'http://localhost:4000/graphql',
});
(async () => {
let cancel = () => {
};
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 }',
});
onUserLeavePage(() => {
cancel();
});
const result = await request;
expect(result).toBe({ data: { hello: 'world' } });
})();
🔗 Client usage with Observable
import { Observable } from 'relay-runtime';
import { Observable } from '@apollo/client/core';
import { Observable } from 'rxjs';
import Observable from 'zen-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' } });
},
});
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) {
return false;
}
if (err.response?.status === 503) {
await new Promise((resolve) => setTimeout(resolve, 1000));
return true;
}
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',
});
</script>
</body>
</html>
🔗 Client usage in Node
const fetch = require('node-fetch');
const { AbortController } = require('node-abort-controller');
const { createClient } = require('graphql-http');
const client = createClient({
url: 'http://no.browser:4000/graphql',
fetchFn: fetch,
abortControllerImpl: AbortController,
});
🔗 Client usage in Deno
import { createClient } from 'https://esm.sh/graphql-http';
const client = createClient({
url: 'http://deno.earth:4000/graphql',
});
🔗 Client usage in Bun
import { createClient } from 'graphql-http';
const client = createClient({
url: 'http://bun.bread:4000/graphql',
});
🔗 Server handler migration from express-graphql
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) 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) => {
const userId = await getUserFromCookies(req.headers.cookie);
const userId = await getUserFromAuthorizationHeader(
req.headers.authorization,
);
if (!userId) {
return [null, { status: 401, statusText: 'Unauthorized' }];
}
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);
},
});
🔗 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) => {
return { userId: req.raw.userId };
},
});
const server = http.createServer(async (req, res) => {
if (!req.url.startsWith('/graphql')) {
return res.writeHead(404).end();
}
try {
req.userId = await getUserFromCookies(req.headers.cookie);
req.userId = await getUserFromAuthorizationHeader(
req.headers.authorization,
);
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');
🔗 Audit for servers usage in Jest environment
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);
}
});
}
🔗 Audit for servers usage in Deno environment
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);
}
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
.
This is the official GraphQL over HTTP spec 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.
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 is checked automatically and updated regularly.
Check the docs folder out for TypeDoc generated documentation.
Inspect audits of other implementations in the implementations folder.
Adding your implementation is very welcome, see how!
Want to help?
File a bug, contribute with code, or improve documentation? Read more in 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.