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';
import { schema } from './previous-step';
const handler = createHandler({ schema });
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');
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';
const handler = createHandler({ schema });
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');
import express from 'express';
import { createHandler } from 'graphql-http';
import { schema } from './previous-step';
const handler = createHandler({ schema });
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');
import Fastify from 'fastify';
import { createHandler } from 'graphql-http';
import { schema } from './previous-step';
const handler = createHandler({ schema });
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: 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);
}
});
fastify.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';
import { schema } from './previous-step';
const handler = createHandler<Request>({ schema });
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,
},
);
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 'graphql-http';
const client = createClient({
url: 'http://deno.earth:4000/graphql',
});
🔗 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: 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!