Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
graphql-http
Advanced tools
Simple, pluggable, zero-dependency, GraphQL over HTTP Protocol compliant server and client
Want a full-featured server? See the servers section!
Need subscriptions? Try graphql-ws or graphql-sse instead!
yarn add graphql-http
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',
},
},
}),
});
http
import http from 'http';
import { createHandler } from 'graphql-http/lib/use/node';
import { schema } from './previous-step';
// Create the GraphQL over HTTP Node request handler
const handler = createHandler({ schema });
// Create a HTTP server using the listner 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');
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/lib/use/node';
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');
express
import express from 'express'; // yarn add express
import { createHandler } from 'graphql-http/lib/use/express';
import { schema } from './previous-step';
// Create a 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');
fastify
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');
Deno
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
},
);
Bun
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 });
}
},
};
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' });
})();
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' } });
})();
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();
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);
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}`,
};
}
},
});
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;
},
});
<!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>
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
import { createClient } from 'https://esm.sh/graphql-http';
const client = createClient({
url: 'http://deno.earth:4000/graphql',
});
// consider other recipes for usage inspiration
import { createClient } from 'graphql-http'; // bun install graphql-http
const client = createClient({
url: 'http://bun.bread:4000/graphql',
});
// consider other recipes for usage inspiration
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);
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 };
},
});
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
});
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;
},
});
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: () =>
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 { 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'
});
}
This is the official GraphQL over HTTP spec reference implementation and as such follows the specification strictly without any additional features (like 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.
Name | Audit |
---|---|
graphql-yoga | ✅ Compliant |
hotchocolate | ✅ Compliant |
postgraphile | ✅ Compliant |
apollo-server | ✅ Compliant |
Check the docs folder out for TypeDoc generated documentation.
Inspect audits of other implementations in the implementations folder. Adding your implementation is very welcome!
File a bug, contribute with code, or improve documentation? Read up on our guidelines below and drive development with yarn test --watch
away!
This repository is managed by EasyCLA. Project participants must sign the free GraphQL Specification Membership agreement before making a contribution. You only need to do this one time, and it can be signed by individual contributors or their employers.
To initiate the signature process please open a PR against this repo. The EasyCLA bot will block the merge if we still need a membership agreement from you.
You can find detailed information here. If you have issues, please email operations@graphql.org.
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.
FAQs
Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.
The npm package graphql-http receives a total of 0 weekly downloads. As such, graphql-http popularity was classified as not popular.
We found that graphql-http demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.