Skip to content

Commit 99b888b

Browse files
authored
feat: Implement server handler (#2)
1 parent 5862251 commit 99b888b

File tree

17 files changed

+2120
-0
lines changed

17 files changed

+2120
-0
lines changed

README.md

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,337 @@
1818
yarn add graphql-http
1919
```
2020

21+
#### Create a GraphQL schema
22+
23+
```js
24+
import { GraphQLSchema, GraphQLObjectType, GraphQLString } from 'graphql';
25+
26+
/**
27+
* Construct a GraphQL schema and define the necessary resolvers.
28+
*
29+
* type Query {
30+
* hello: String
31+
* }
32+
*/
33+
const schema = new GraphQLSchema({
34+
query: new GraphQLObjectType({
35+
name: 'Query',
36+
fields: {
37+
hello: {
38+
type: GraphQLString,
39+
resolve: () => 'world',
40+
},
41+
},
42+
}),
43+
});
44+
```
45+
46+
#### Start the server
47+
48+
##### With [`http`](https://nodejs.org/api/http.html)
49+
50+
```js
51+
import http from 'http';
52+
import { createHandler } from 'graphql-http';
53+
import { schema } from './previous-step';
54+
55+
// Create the GraphQL over HTTP handler
56+
const handler = createHandler({ schema });
57+
58+
// Create a HTTP server using the handler on `/graphql`
59+
const server = http.createServer(async (req, res) => {
60+
if (!req.url.startsWith('/graphql')) {
61+
return res.writeHead(404).end();
62+
}
63+
64+
try {
65+
const [body, init] = await handler({
66+
url: req.url,
67+
method: req.method,
68+
headers: req.headers,
69+
body: await new Promise((resolve) => {
70+
let body = '';
71+
req.on('data', (chunk) => (body += chunk));
72+
req.on('end', () => resolve(body));
73+
}),
74+
raw: req,
75+
});
76+
res.writeHead(init.status, init.statusText, init.headers).end(body);
77+
} catch (err) {
78+
res.writeHead(500).end(err.message);
79+
}
80+
});
81+
82+
server.listen(4000);
83+
console.log('Listening to port 4000');
84+
```
85+
86+
##### With [`http2`](https://nodejs.org/api/http2.html)
87+
88+
_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)_
89+
90+
```shell
91+
$ openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \
92+
-keyout localhost-privkey.pem -out localhost-cert.pem
93+
```
94+
95+
```js
96+
import fs from 'fs';
97+
import http2 from 'http2';
98+
import { createHandler } from 'graphql-http';
99+
import { schema } from './previous-step';
100+
101+
// Create the GraphQL over HTTP handler
102+
const handler = createHandler({ schema });
103+
104+
// Create a HTTP/2 server using the handler on `/graphql`
105+
const server = http2.createSecureServer(
106+
{
107+
key: fs.readFileSync('localhost-privkey.pem'),
108+
cert: fs.readFileSync('localhost-cert.pem'),
109+
},
110+
async (req, res) => {
111+
if (!req.url.startsWith('/graphql')) {
112+
return res.writeHead(404).end();
113+
}
114+
115+
try {
116+
const [body, init] = await handler({
117+
url: req.url,
118+
method: req.method,
119+
headers: req.headers,
120+
body: await new Promise((resolve) => {
121+
let body = '';
122+
req.on('data', (chunk) => (body += chunk));
123+
req.on('end', () => resolve(body));
124+
}),
125+
raw: req,
126+
});
127+
res.writeHead(init.status, init.statusText, init.headers).end(body);
128+
} catch (err) {
129+
res.writeHead(500).end(err.message);
130+
}
131+
},
132+
);
133+
134+
server.listen(4000);
135+
console.log('Listening to port 4000');
136+
```
137+
138+
##### With [`express`](https://expressjs.com/)
139+
140+
```js
141+
import express from 'express'; // yarn add express
142+
import { createHandler } from 'graphql-http';
143+
import { schema } from './previous-step';
144+
145+
// Create the GraphQL over HTTP handler
146+
const handler = createHandler({ schema });
147+
148+
// Create an express app serving all methods on `/graphql`
149+
const app = express();
150+
app.use('/graphql', async (req, res) => {
151+
try {
152+
const [body, init] = await handler({
153+
url: req.url,
154+
method: req.method,
155+
headers: req.headers,
156+
body: await new Promise((resolve) => {
157+
let body = '';
158+
req.on('data', (chunk) => (body += chunk));
159+
req.on('end', () => resolve(body));
160+
}),
161+
raw: req,
162+
});
163+
res.writeHead(init.status, init.statusText, init.headers).end(body);
164+
} catch (err) {
165+
res.writeHead(500).end(err.message);
166+
}
167+
});
168+
169+
app.listen(4000);
170+
console.log('Listening to port 4000');
171+
```
172+
173+
##### With [`fastify`](https://www.fastify.io/)
174+
175+
```js
176+
import Fastify from 'fastify'; // yarn add fastify
177+
import { createHandler } from 'graphql-http';
178+
import { schema } from './previous-step';
179+
180+
// Create the GraphQL over HTTP handler
181+
const handler = createHandler({ schema });
182+
183+
// Create a fastify instance serving all methods on `/graphql`
184+
const fastify = Fastify();
185+
fastify.all('/graphql', async (req, res) => {
186+
try {
187+
const [body, init] = await handler({
188+
url: req.url,
189+
method: req.method,
190+
headers: req.headers,
191+
body: await new Promise((resolve) => {
192+
let body = '';
193+
req.on('data', (chunk) => (body += chunk));
194+
req.on('end', () => resolve(body));
195+
}),
196+
raw: req,
197+
});
198+
res.writeHead(init.status, init.statusText, init.headers).end(body);
199+
} catch (err) {
200+
res.writeHead(500).end(err.message);
201+
}
202+
});
203+
204+
fastify.listen(4000);
205+
console.log('Listening to port 4000');
206+
```
207+
208+
## Recipes
209+
210+
<details id="auth">
211+
<summary><a href="#auth">🔗</a> Server handler usage with authentication</summary>
212+
213+
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.
214+
215+
```js
216+
import { createHandler } from 'graphql-http';
217+
import {
218+
schema,
219+
getUserFromCookies,
220+
getUserFromAuthorizationHeader,
221+
} from './my-graphql';
222+
223+
const handler = createHandler({
224+
schema,
225+
context: async (req) => {
226+
// process token, authenticate user and attach it to your graphql context
227+
const userId = await getUserFromCookies(req.headers.cookie);
228+
// or
229+
const userId = await getUserFromAuthorizationHeader(
230+
req.headers.authorization,
231+
);
232+
233+
// respond with 401 if the user was not authenticated
234+
if (!userId) {
235+
return [null, { status: 401, statusText: 'Unauthorized' }];
236+
}
237+
238+
// otherwise attach the user to the graphql context
239+
return { userId };
240+
},
241+
});
242+
```
243+
244+
</details>
245+
246+
<details id="context">
247+
<summary><a href="#context">🔗</a> Server handler usage with custom context value</summary>
248+
249+
```js
250+
import { createHandler } from 'graphql-http';
251+
import { schema, getDynamicContext } from './my-graphql';
252+
253+
const handler = createHandler({
254+
schema,
255+
context: async (req, args) => {
256+
return getDynamicContext(req, args);
257+
},
258+
// or static context by supplying the value direcly
259+
});
260+
```
261+
262+
</details>
263+
264+
<details id="custom-exec">
265+
<summary><a href="#custom-exec">🔗</a> Server handler usage with custom execution arguments</summary>
266+
267+
```js
268+
import { parse } from 'graphql';
269+
import { createHandler } from 'graphql-http';
270+
import { getSchemaForRequest, myValidationRules } from './my-graphql';
271+
272+
const handler = createHandler({
273+
onSubscribe: async (req, params) => {
274+
const schema = await getSchemaForRequest(req);
275+
276+
const args = {
277+
schema,
278+
operationName: params.operationName,
279+
document: parse(params.query),
280+
variableValues: params.variables,
281+
};
282+
283+
return args;
284+
},
285+
});
286+
```
287+
288+
</details>
289+
290+
<details id="auth-node-early">
291+
<summary><a href="#auth-node-early">🔗</a> Server handler usage in Node with early authentication (recommended)</summary>
292+
293+
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.
294+
295+
```js
296+
import { createHandler } from 'graphql-http';
297+
import {
298+
schema,
299+
getUserFromCookies,
300+
getUserFromAuthorizationHeader,
301+
} from './my-graphql';
302+
303+
const handler = createHandler({
304+
schema,
305+
context: async (req) => {
306+
// user is authenticated early (see below), simply attach it to the graphql context
307+
return { userId: req.raw.userId };
308+
},
309+
});
310+
311+
const server = http.createServer(async (req, res) => {
312+
if (!req.url.startsWith('/graphql')) {
313+
return res.writeHead(404).end();
314+
}
315+
316+
try {
317+
// process token, authenticate user and attach it to the request
318+
req.userId = await getUserFromCookies(req.headers.cookie);
319+
// or
320+
req.userId = await getUserFromAuthorizationHeader(
321+
req.headers.authorization,
322+
);
323+
324+
// respond with 401 if the user was not authenticated
325+
if (!req.userId) {
326+
return res.writeHead(401, 'Unauthorized').end();
327+
}
328+
329+
const [body, init] = await handler({
330+
url: req.url,
331+
method: req.method,
332+
headers: req.headers,
333+
body: await new Promise((resolve) => {
334+
let body = '';
335+
req.on('data', (chunk) => (body += chunk));
336+
req.on('end', () => resolve(body));
337+
}),
338+
raw: req,
339+
});
340+
res.writeHead(init.status, init.statusText, init.headers).end(body);
341+
} catch (err) {
342+
res.writeHead(500).end(err.message);
343+
}
344+
});
345+
346+
server.listen(4000);
347+
console.log('Listening to port 4000');
348+
```
349+
350+
</details>
351+
21352
## [Documentation](docs/)
22353

23354
Check the [docs folder](docs/) out for [TypeDoc](https://typedoc.org) generated documentation.

0 commit comments

Comments
 (0)