Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/graphql/graphql-http
Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.
https://github.com/graphql/graphql-http
apollo client express fastify graphql http observables relay server the-guild transport
Last synced: 29 days ago
JSON representation
Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.
- Host: GitHub
- URL: https://github.com/graphql/graphql-http
- Owner: graphql
- License: mit
- Created: 2022-05-16T12:13:17.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2024-08-27T10:39:03.000Z (2 months ago)
- Last Synced: 2024-09-28T11:22:59.893Z (about 1 month ago)
- Topics: apollo, client, express, fastify, graphql, http, observables, relay, server, the-guild, transport
- Language: TypeScript
- Homepage: https://graphql-http.com
- Size: 9.49 MB
- Stars: 303
- Watchers: 10
- Forks: 20
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE.md
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
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/[email protected]/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();
``````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);
``````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
const client = graphqlHttp.createClient({
url: 'http://umdfor.the:4000/win/graphql',
});// consider other recipes for usage inspiration
```
🔗 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-httpconst 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).