Skip to content

Commit e3b85ff

Browse files
authored
feat(use): Add Koa integration (#33)
1 parent 48aefef commit e3b85ff

File tree

5 files changed

+562
-125
lines changed

5 files changed

+562
-125
lines changed

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,21 @@ export default {
190190
};
191191
```
192192

193+
##### With [`Koa`](https://koajs.com/)
194+
195+
```js
196+
import Koa from 'koa'; // yarn add koa
197+
import mount from 'koa-mount'; // yarn add koa-mount
198+
import { createHandler } from 'graphql-http/lib/use/koa';
199+
import { schema } from './previous-step';
200+
201+
const app = new Koa();
202+
app.use(mount('/graphql', createHandler({ schema })));
203+
204+
app.listen({ port: 4000 });
205+
console.log('Listening to port 4000');
206+
```
207+
193208
#### Use the client
194209

195210
```js

package.json

+9
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@
5555
"require": "./lib/use/fastify.js",
5656
"import": "./lib/use/fastify.mjs"
5757
},
58+
"./lib/use/koa": {
59+
"types": "./lib/use/koa.d.ts",
60+
"require": "./lib/use/koa.js",
61+
"import": "./lib/use/koa.mjs"
62+
},
5863
"./package.json": "./package.json"
5964
},
6065
"types": "lib/index.d.ts",
@@ -105,6 +110,8 @@
105110
"@types/express": "^4.17.15",
106111
"@types/glob": "^8.0.0",
107112
"@types/jest": "^29.2.4",
113+
"@types/koa": "^2.13.5",
114+
"@types/koa-mount": "^4.0.2",
108115
"@typescript-eslint/eslint-plugin": "^5.47.0",
109116
"@typescript-eslint/parser": "^5.47.0",
110117
"@whatwg-node/fetch": "^0.5.3",
@@ -116,6 +123,8 @@
116123
"graphql": "^16.6.0",
117124
"jest": "^29.3.1",
118125
"jest-jasmine2": "^29.3.1",
126+
"koa": "^2.14.1",
127+
"koa-mount": "^4.0.0",
119128
"node-fetch": "^3.3.0",
120129
"prettier": "^2.8.1",
121130
"rollup": "^3.8.1",

src/__tests__/use.ts

+20
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { fetch } from '@whatwg-node/fetch';
22
import http from 'http';
33
import express from 'express';
44
import fastify from 'fastify';
5+
import Koa from 'koa';
6+
import mount from 'koa-mount';
57
import { createServerAdapter } from '@whatwg-node/server';
68
import { startDisposableServer } from './utils/tserver';
79
import { serverAudits } from '../audits';
@@ -11,6 +13,7 @@ import { createHandler as createNodeHandler } from '../use/node';
1113
import { createHandler as createExpressHandler } from '../use/express';
1214
import { createHandler as createFastifyHandler } from '../use/fastify';
1315
import { createHandler as createFetchHandler } from '../use/fetch';
16+
import { createHandler as createKoaHandler } from '../use/koa';
1417

1518
describe('node', () => {
1619
const [url, dispose] = startDisposableServer(
@@ -90,3 +93,20 @@ describe('fetch', () => {
9093
});
9194
}
9295
});
96+
97+
describe('koa', () => {
98+
const app = new Koa();
99+
app.use(mount('/', createKoaHandler({ schema })));
100+
101+
const [url, dispose] = startDisposableServer(app.listen(0));
102+
afterAll(dispose);
103+
104+
for (const audit of serverAudits({ url, fetchFn: fetch })) {
105+
it(audit.name, async () => {
106+
const result = await audit.fn();
107+
if (result.status !== 'ok') {
108+
throw result.reason;
109+
}
110+
});
111+
}
112+
});

src/use/koa.ts

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import type { Middleware } from 'koa';
2+
import type { IncomingMessage } from 'http';
3+
import {
4+
createHandler as createRawHandler,
5+
HandlerOptions,
6+
OperationContext,
7+
} from '../handler';
8+
9+
/**
10+
* Create a GraphQL over HTTP Protocol compliant request handler for
11+
* the Koa framework.
12+
*
13+
* ```js
14+
* import Koa from 'koa'; // yarn add koa
15+
* import mount from 'koa-mount'; // yarn add koa-mount
16+
* import { createHandler } from 'graphql-http/lib/use/koa';
17+
* import { schema } from './my-graphql-schema';
18+
*
19+
* const app = new Koa();
20+
* app.use(mount('/', createHandler({ schema })));
21+
*
22+
* app.listen({ port: 4000 });
23+
* console.log('Listening to port 4000');
24+
* ```
25+
*
26+
* @category Server/koa
27+
*/
28+
export function createHandler<Context extends OperationContext = undefined>(
29+
options: HandlerOptions<IncomingMessage, undefined, Context>,
30+
): Middleware {
31+
const isProd = process.env.NODE_ENV === 'production';
32+
const handle = createRawHandler(options);
33+
return async function requestListener(ctx) {
34+
try {
35+
const [body, init] = await handle({
36+
url: ctx.url,
37+
method: ctx.method,
38+
headers: ctx.headers,
39+
body: () => {
40+
if (ctx.body) {
41+
// in case koa has a body parser
42+
return ctx.body;
43+
}
44+
return new Promise<string>((resolve) => {
45+
let body = '';
46+
ctx.req.on('data', (chunk) => (body += chunk));
47+
ctx.req.on('end', () => resolve(body));
48+
});
49+
},
50+
raw: ctx.req,
51+
context: undefined,
52+
});
53+
ctx.body = body;
54+
ctx.response.status = init.status;
55+
ctx.response.message = init.statusText;
56+
if (init.headers) {
57+
for (const [name, value] of Object.entries(init.headers)) {
58+
ctx.response.set(name, value);
59+
}
60+
}
61+
} catch (err) {
62+
// The handler shouldnt throw errors.
63+
// If you wish to handle them differently, consider implementing your own request handler.
64+
console.error(
65+
'Internal error occurred during request handling. ' +
66+
'Please check your implementation.',
67+
err,
68+
);
69+
ctx.response.status = 500;
70+
if (!isProd) {
71+
ctx.response.set('content-type', 'application/json; charset=utf-8');
72+
ctx.body = {
73+
errors: [
74+
err instanceof Error
75+
? {
76+
message: err.message,
77+
stack: err.stack,
78+
}
79+
: err,
80+
],
81+
};
82+
}
83+
}
84+
};
85+
}

0 commit comments

Comments
 (0)