diff --git a/README.md b/README.md index 7ad2f780..9e9fe2cb 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ const schema = new GraphQLSchema({ ```js import http from 'http'; -import { createHandler } from 'graphql-http/lib/use/node'; +import { createHandler } from 'graphql-http/lib/use/http'; import { schema } from './previous-step'; // Create the GraphQL over HTTP Node request handler @@ -86,7 +86,7 @@ $ openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \ ```js import fs from 'fs'; import http2 from 'http2'; -import { createHandler } from 'graphql-http/lib/use/node'; +import { createHandler } from 'graphql-http/lib/use/http2'; import { schema } from './previous-step'; // Create the GraphQL over HTTP Node request handler diff --git a/docs/README.md b/docs/README.md index 98efa70e..9df702bd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,5 +15,7 @@ graphql-http - [use/express](modules/use_express.md) - [use/fastify](modules/use_fastify.md) - [use/fetch](modules/use_fetch.md) +- [use/http](modules/use_http.md) +- [use/http2](modules/use_http2.md) - [use/koa](modules/use_koa.md) - [use/node](modules/use_node.md) diff --git a/docs/modules/use_http.md b/docs/modules/use_http.md new file mode 100644 index 00000000..f7fc37de --- /dev/null +++ b/docs/modules/use_http.md @@ -0,0 +1,76 @@ +[graphql-http](../README.md) / use/http + +# Module: use/http + +## Table of contents + +### Type Aliases + +- [HandlerOptions](use_http.md#handleroptions) + +### Functions + +- [createHandler](use_http.md#createhandler) + +## Server/http + +### HandlerOptions + +Ƭ **HandlerOptions**<`Context`\>: [`HandlerOptions`](../interfaces/handler.HandlerOptions.md)<`IncomingMessage`, `undefined`, `Context`\> + +Handler options when using the http adapter. + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Context` | extends [`OperationContext`](handler.md#operationcontext) = `undefined` | + +___ + +### createHandler + +▸ **createHandler**<`Context`\>(`options`): (`req`: `IncomingMessage`, `res`: `ServerResponse`) => `Promise`<`void`\> + +Create a GraphQL over HTTP Protocol compliant request handler for +the Node environment http module. + +```js +import http from 'http'; +import { createHandler } from 'graphql-http/lib/use/http'; +import { schema } from './my-graphql-step'; + +const server = http.createServer(createHandler({ schema })); + +server.listen(4000); +console.log('Listening to port 4000'); +``` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Context` | extends [`OperationContext`](handler.md#operationcontext) = `undefined` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `options` | [`HandlerOptions`](use_http.md#handleroptions)<`Context`\> | + +#### Returns + +`fn` + +▸ (`req`, `res`): `Promise`<`void`\> + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `req` | `IncomingMessage` | +| `res` | `ServerResponse` | + +##### Returns + +`Promise`<`void`\> diff --git a/docs/modules/use_http2.md b/docs/modules/use_http2.md new file mode 100644 index 00000000..05a92f92 --- /dev/null +++ b/docs/modules/use_http2.md @@ -0,0 +1,88 @@ +[graphql-http](../README.md) / use/http2 + +# Module: use/http2 + +## Table of contents + +### Type Aliases + +- [HandlerOptions](use_http2.md#handleroptions) + +### Functions + +- [createHandler](use_http2.md#createhandler) + +## Server/http2 + +### HandlerOptions + +Ƭ **HandlerOptions**<`Context`\>: [`HandlerOptions`](../interfaces/handler.HandlerOptions.md)<`Http2ServerRequest`, `undefined`, `Context`\> + +Handler options when using the http adapter. + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Context` | extends [`OperationContext`](handler.md#operationcontext) = `undefined` | + +___ + +### createHandler + +▸ **createHandler**<`Context`\>(`options`): (`req`: `Http2ServerRequest`, `res`: `Http2ServerResponse`) => `Promise`<`void`\> + +Create a GraphQL over HTTP Protocol compliant request handler for +the Node environment http2 module. + + ```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 './my-graphql-step'; + +const server = http2.createSecureServer( + { + key: fs.readFileSync('localhost-privkey.pem'), + cert: fs.readFileSync('localhost-cert.pem'), + }, + createHandler({ schema }), +); + +server.listen(4000); +console.log('Listening to port 4000'); +``` + +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `Context` | extends [`OperationContext`](handler.md#operationcontext) = `undefined` | + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `options` | [`HandlerOptions`](use_http2.md#handleroptions)<`Context`\> | + +#### Returns + +`fn` + +▸ (`req`, `res`): `Promise`<`void`\> + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `req` | `Http2ServerRequest` | +| `res` | `Http2ServerResponse` | + +##### Returns + +`Promise`<`void`\> diff --git a/docs/modules/use_node.md b/docs/modules/use_node.md index 3ef30fc9..ffbe3452 100644 --- a/docs/modules/use_node.md +++ b/docs/modules/use_node.md @@ -16,10 +16,14 @@ ### HandlerOptions -Ƭ **HandlerOptions**<`Context`\>: [`HandlerOptions`](../interfaces/handler.HandlerOptions.md)<`IncomingMessage`, `undefined`, `Context`\> +Ƭ **HandlerOptions**<`Context`\>: [`HandlerOptions`](use_http.md#handleroptions)<`Context`\> Handler options when using the node adapter. +**`Deprecated`** + +Please use [http](use_http.md#handleroptions) or [http2](use_http2.md#handleroptions) adapters instead. + #### Type parameters | Name | Type | @@ -30,7 +34,7 @@ ___ ### createHandler -▸ **createHandler**<`Context`\>(`options`): `RequestListener` +▸ **createHandler**<`Context`\>(`options`): (`req`: `IncomingMessage`, `res`: `ServerResponse`<`IncomingMessage`\>) => `Promise`<`void`\> Create a GraphQL over HTTP Protocol compliant request handler for the Node environment. @@ -46,6 +50,10 @@ server.listen(4000); console.log('Listening to port 4000'); ``` +**`Deprecated`** + +Please use [http](use_http.md#createhandler) or [http2](use_http2.md#createhandler) adapters instead. + #### Type parameters | Name | Type | @@ -60,4 +68,35 @@ console.log('Listening to port 4000'); #### Returns -`RequestListener` +`fn` + +▸ (`req`, `res`): `Promise`<`void`\> + +Create a GraphQL over HTTP Protocol compliant request handler for +the Node environment http module. + +```js +import http from 'http'; +import { createHandler } from 'graphql-http/lib/use/http'; +import { schema } from './my-graphql-step'; + +const server = http.createServer(createHandler({ schema })); + +server.listen(4000); +console.log('Listening to port 4000'); +``` + +**`Category`** + +Server/http + +##### Parameters + +| Name | Type | +| :------ | :------ | +| `req` | `IncomingMessage` | +| `res` | `ServerResponse`<`IncomingMessage`\> | + +##### Returns + +`Promise`<`void`\> diff --git a/package.json b/package.json index 11c637f7..7e4efdbb 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,16 @@ "require": "./lib/use/node.js", "import": "./lib/use/node.mjs" }, + "./lib/use/http": { + "types": "./lib/use/http.d.ts", + "require": "./lib/use/http.js", + "import": "./lib/use/http.mjs" + }, + "./lib/use/http2": { + "types": "./lib/use/http2.d.ts", + "require": "./lib/use/http2.js", + "import": "./lib/use/http2.mjs" + }, "./lib/use/express": { "types": "./lib/use/express.d.ts", "require": "./lib/use/express.js", diff --git a/src/__tests__/use.ts b/src/__tests__/use.ts index d4043b3c..9e3c86d7 100644 --- a/src/__tests__/use.ts +++ b/src/__tests__/use.ts @@ -9,15 +9,15 @@ import { startDisposableServer } from './utils/tserver'; import { serverAudits } from '../audits'; import { schema } from './fixtures/simple'; -import { createHandler as createNodeHandler } from '../use/node'; +import { createHandler as createHttpHandler } from '../use/http'; import { createHandler as createExpressHandler } from '../use/express'; import { createHandler as createFastifyHandler } from '../use/fastify'; import { createHandler as createFetchHandler } from '../use/fetch'; import { createHandler as createKoaHandler } from '../use/koa'; -describe('node', () => { +describe('http', () => { const [url, dispose] = startDisposableServer( - http.createServer(createNodeHandler({ schema })), + http.createServer(createHttpHandler({ schema })), ); afterAll(dispose); @@ -31,6 +31,10 @@ describe('node', () => { } }); +describe('http2', () => { + it.todo('should pass all server audits'); +}); + describe('express', () => { const app = express(); app.all('/', createExpressHandler({ schema })); diff --git a/src/use/http.ts b/src/use/http.ts new file mode 100644 index 00000000..dcf0fb70 --- /dev/null +++ b/src/use/http.ts @@ -0,0 +1,88 @@ +import type { IncomingMessage, ServerResponse } from 'http'; +import { + createHandler as createRawHandler, + HandlerOptions as RawHandlerOptions, + OperationContext, +} from '../handler'; + +/** + * Handler options when using the http adapter. + * + * @category Server/http + */ +export type HandlerOptions = + RawHandlerOptions; + +/** + * Create a GraphQL over HTTP Protocol compliant request handler for + * the Node environment http module. + * + * ```js + * import http from 'http'; + * import { createHandler } from 'graphql-http/lib/use/http'; + * import { schema } from './my-graphql-step'; + * + * const server = http.createServer(createHandler({ schema })); + * + * server.listen(4000); + * console.log('Listening to port 4000'); + * ``` + * + * @category Server/http + */ +export function createHandler( + options: HandlerOptions, +): (req: IncomingMessage, res: ServerResponse) => Promise { + const isProd = process.env.NODE_ENV === 'production'; + const handle = createRawHandler(options); + return async function requestListener(req, res) { + try { + if (!req.url) { + throw new Error('Missing request URL'); + } + if (!req.method) { + throw new Error('Missing request method'); + } + const [body, init] = await handle({ + 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, + context: undefined, + }); + res.writeHead(init.status, init.statusText, init.headers).end(body); + } catch (err) { + // The handler shouldnt throw errors. + // If you wish to handle them differently, consider implementing your own request handler. + console.error( + 'Internal error occurred during request handling. ' + + 'Please check your implementation.', + err, + ); + if (isProd) { + res.writeHead(500).end(); + } else { + res + .writeHead(500, { 'content-type': 'application/json; charset=utf-8' }) + .end( + JSON.stringify({ + errors: [ + err instanceof Error + ? { + message: err.message, + stack: err.stack, + } + : err, + ], + }), + ); + } + } + }; +} diff --git a/src/use/http2.ts b/src/use/http2.ts new file mode 100644 index 00000000..b786dcba --- /dev/null +++ b/src/use/http2.ts @@ -0,0 +1,105 @@ +import type { Http2ServerRequest, Http2ServerResponse } from 'http2'; +import { + createHandler as createRawHandler, + HandlerOptions as RawHandlerOptions, + OperationContext, +} from '../handler'; + +/** + * Handler options when using the http adapter. + * + * @category Server/http2 + */ +export type HandlerOptions = + RawHandlerOptions; + +/** + * Create a GraphQL over HTTP Protocol compliant request handler for + * the Node environment http2 module. + * + * ```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 './my-graphql-step'; + * + * const server = http2.createSecureServer( + * { + * key: fs.readFileSync('localhost-privkey.pem'), + * cert: fs.readFileSync('localhost-cert.pem'), + * }, + * createHandler({ schema }), + * ); + * + * server.listen(4000); + * console.log('Listening to port 4000'); + * ``` + * + * @category Server/http2 + */ +export function createHandler( + options: HandlerOptions, +): (req: Http2ServerRequest, res: Http2ServerResponse) => Promise { + const isProd = process.env.NODE_ENV === 'production'; + const handle = createRawHandler(options); + return async function requestListener(req, res) { + try { + if (!req.url) { + throw new Error('Missing request URL'); + } + if (!req.method) { + throw new Error('Missing request method'); + } + const [body, init] = await handle({ + 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, + context: undefined, + }); + res.writeHead(init.status, init.statusText, init.headers); + if (body) { + res.end(body); + } else { + res.end(); + } + } catch (err) { + // The handler shouldnt throw errors. + // If you wish to handle them differently, consider implementing your own request handler. + console.error( + 'Internal error occurred during request handling. ' + + 'Please check your implementation.', + err, + ); + if (isProd) { + res.writeHead(500).end(); + } else { + res + .writeHead(500, { 'content-type': 'application/json; charset=utf-8' }) + .end( + JSON.stringify({ + errors: [ + err instanceof Error + ? { + message: err.message, + stack: err.stack, + } + : err, + ], + }), + ); + } + } + }; +} diff --git a/src/use/node.ts b/src/use/node.ts index cae6d03f..bba3fb49 100644 --- a/src/use/node.ts +++ b/src/use/node.ts @@ -1,17 +1,18 @@ -import type { IncomingMessage, RequestListener } from 'http'; import { - createHandler as createRawHandler, - HandlerOptions as RawHandlerOptions, - OperationContext, -} from '../handler'; + createHandler as httpCreateHandler, + HandlerOptions as HttpHandlerOptions, +} from './http'; +import { OperationContext } from '../handler'; /** * Handler options when using the node adapter. * * @category Server/node + * + * @deprecated Please use {@link use/http.HandlerOptions | http} or {@link use/http2.HandlerOptions | http2} adapters instead. */ export type HandlerOptions = - RawHandlerOptions; + HttpHandlerOptions; /** * Create a GraphQL over HTTP Protocol compliant request handler for @@ -29,60 +30,11 @@ export type HandlerOptions = * ``` * * @category Server/node + * + * @deprecated Please use {@link use/http.createHandler | http} or {@link use/http2.createHandler | http2} adapters instead. */ export function createHandler( options: HandlerOptions, -): RequestListener { - const isProd = process.env.NODE_ENV === 'production'; - const handle = createRawHandler(options); - return async function requestListener(req, res) { - try { - if (!req.url) { - throw new Error('Missing request URL'); - } - if (!req.method) { - throw new Error('Missing request method'); - } - const [body, init] = await handle({ - 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, - context: undefined, - }); - res.writeHead(init.status, init.statusText, init.headers).end(body); - } catch (err) { - // The handler shouldnt throw errors. - // If you wish to handle them differently, consider implementing your own request handler. - console.error( - 'Internal error occurred during request handling. ' + - 'Please check your implementation.', - err, - ); - if (isProd) { - res.writeHead(500).end(); - } else { - res - .writeHead(500, { 'content-type': 'application/json; charset=utf-8' }) - .end( - JSON.stringify({ - errors: [ - err instanceof Error - ? { - message: err.message, - stack: err.stack, - } - : err, - ], - }), - ); - } - } - }; +) { + return httpCreateHandler(options); }