graphql-http 0.1.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{LICENSE → LICENSE.md} +2 -2
- package/README.md +693 -30
- package/lib/client.d.ts +155 -0
- package/lib/client.js +189 -0
- package/lib/client.mjs +170 -0
- package/lib/common.d.ts +108 -0
- package/lib/common.js +21 -0
- package/lib/common.mjs +17 -0
- package/lib/handler.d.ts +166 -0
- package/lib/handler.js +358 -0
- package/lib/handler.mjs +354 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +18 -184
- package/lib/index.mjs +3 -0
- package/lib/utils.d.ts +10 -0
- package/lib/utils.js +22 -0
- package/lib/utils.mjs +17 -0
- package/package.json +87 -25
- package/umd/graphql-http.js +208 -0
- package/umd/graphql-http.min.js +1 -0
- package/umd/graphql-http.min.js.gz +0 -0
- package/.eslintrc.json +0 -8
- package/.npmignore +0 -33
- package/index.js +0 -1
- package/lib/__tests__/index.js +0 -66
package/README.md
CHANGED
|
@@ -1,53 +1,716 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<br />
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
<h3>graphql-http</h3>
|
|
4
5
|
|
|
6
|
+
<h6>Simple, pluggable, zero-dependency, <a href="https://graphql.github.io/graphql-over-http">GraphQL over HTTP Protocol</a> compliant server and client.</h6>
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
[](https://github.com/enisdenjo/graphql-http/actions?query=workflow%3A%22Continuous+integration%22) [](https://www.npmjs.com/package/graphql-http)
|
|
9
|
+
|
|
10
|
+
<i>Need subscriptions? Try <b>[graphql-ws](https://github.com/enisdenjo/graphql-ws)</b> or <b>[graphql-sse](https://github.com/enisdenjo/graphql-sse)</b> instead!</i>
|
|
11
|
+
|
|
12
|
+
<br />
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
## Getting started
|
|
16
|
+
|
|
17
|
+
#### Install
|
|
18
|
+
|
|
19
|
+
```shell
|
|
20
|
+
yarn add graphql-http
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
#### Create a GraphQL schema
|
|
7
24
|
|
|
8
25
|
```js
|
|
9
|
-
import {
|
|
26
|
+
import { GraphQLSchema, GraphQLObjectType, GraphQLString } from 'graphql';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Construct a GraphQL schema and define the necessary resolvers.
|
|
30
|
+
*
|
|
31
|
+
* type Query {
|
|
32
|
+
* hello: String
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
const schema = new GraphQLSchema({
|
|
36
|
+
query: new GraphQLObjectType({
|
|
37
|
+
name: 'Query',
|
|
38
|
+
fields: {
|
|
39
|
+
hello: {
|
|
40
|
+
type: GraphQLString,
|
|
41
|
+
resolve: () => 'world',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
#### Start the server
|
|
49
|
+
|
|
50
|
+
##### With [`http`](https://nodejs.org/api/http.html)
|
|
10
51
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
52
|
+
```js
|
|
53
|
+
import http from 'http';
|
|
54
|
+
import { createHandler } from 'graphql-http';
|
|
55
|
+
import { schema } from './previous-step';
|
|
56
|
+
|
|
57
|
+
// Create the GraphQL over HTTP handler
|
|
58
|
+
const handler = createHandler({ schema });
|
|
59
|
+
|
|
60
|
+
// Create a HTTP server using the handler on `/graphql`
|
|
61
|
+
const server = http.createServer(async (req, res) => {
|
|
62
|
+
if (!req.url.startsWith('/graphql')) {
|
|
63
|
+
return res.writeHead(404).end();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const [body, init] = await handler({
|
|
68
|
+
url: req.url,
|
|
69
|
+
method: req.method,
|
|
70
|
+
headers: req.headers,
|
|
71
|
+
body: await new Promise((resolve) => {
|
|
72
|
+
let body = '';
|
|
73
|
+
req.on('data', (chunk) => (body += chunk));
|
|
74
|
+
req.on('end', () => resolve(body));
|
|
75
|
+
}),
|
|
76
|
+
raw: req,
|
|
77
|
+
});
|
|
78
|
+
res.writeHead(init.status, init.statusText, init.headers).end(body);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
res.writeHead(500).end(err.message);
|
|
17
81
|
}
|
|
18
82
|
});
|
|
83
|
+
|
|
84
|
+
server.listen(4000);
|
|
85
|
+
console.log('Listening to port 4000');
|
|
19
86
|
```
|
|
20
87
|
|
|
21
|
-
|
|
88
|
+
##### With [`http2`](https://nodejs.org/api/http2.html)
|
|
89
|
+
|
|
90
|
+
_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)_
|
|
91
|
+
|
|
92
|
+
```shell
|
|
93
|
+
$ openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \
|
|
94
|
+
-keyout localhost-privkey.pem -out localhost-cert.pem
|
|
95
|
+
```
|
|
22
96
|
|
|
23
97
|
```js
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
98
|
+
import fs from 'fs';
|
|
99
|
+
import http2 from 'http2';
|
|
100
|
+
import { createHandler } from 'graphql-http';
|
|
101
|
+
import { schema } from './previous-step';
|
|
102
|
+
|
|
103
|
+
// Create the GraphQL over HTTP handler
|
|
104
|
+
const handler = createHandler({ schema });
|
|
105
|
+
|
|
106
|
+
// Create a HTTP/2 server using the handler on `/graphql`
|
|
107
|
+
const server = http2.createSecureServer(
|
|
108
|
+
{
|
|
109
|
+
key: fs.readFileSync('localhost-privkey.pem'),
|
|
110
|
+
cert: fs.readFileSync('localhost-cert.pem'),
|
|
111
|
+
},
|
|
112
|
+
async (req, res) => {
|
|
113
|
+
if (!req.url.startsWith('/graphql')) {
|
|
114
|
+
return res.writeHead(404).end();
|
|
29
115
|
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const [body, init] = await handler({
|
|
119
|
+
url: req.url,
|
|
120
|
+
method: req.method,
|
|
121
|
+
headers: req.headers,
|
|
122
|
+
body: await new Promise((resolve) => {
|
|
123
|
+
let body = '';
|
|
124
|
+
req.on('data', (chunk) => (body += chunk));
|
|
125
|
+
req.on('end', () => resolve(body));
|
|
126
|
+
}),
|
|
127
|
+
raw: req,
|
|
128
|
+
});
|
|
129
|
+
res.writeHead(init.status, init.statusText, init.headers).end(body);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
res.writeHead(500).end(err.message);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
server.listen(4000);
|
|
137
|
+
console.log('Listening to port 4000');
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
##### With [`express`](https://expressjs.com/)
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
import express from 'express'; // yarn add express
|
|
144
|
+
import { createHandler } from 'graphql-http';
|
|
145
|
+
import { schema } from './previous-step';
|
|
146
|
+
|
|
147
|
+
// Create the GraphQL over HTTP handler
|
|
148
|
+
const handler = createHandler({ schema });
|
|
149
|
+
|
|
150
|
+
// Create an express app serving all methods on `/graphql`
|
|
151
|
+
const app = express();
|
|
152
|
+
app.use('/graphql', async (req, res) => {
|
|
153
|
+
try {
|
|
154
|
+
const [body, init] = await handler({
|
|
155
|
+
url: req.url,
|
|
156
|
+
method: req.method,
|
|
157
|
+
headers: req.headers,
|
|
158
|
+
body: await new Promise((resolve) => {
|
|
159
|
+
let body = '';
|
|
160
|
+
req.on('data', (chunk) => (body += chunk));
|
|
161
|
+
req.on('end', () => resolve(body));
|
|
162
|
+
}),
|
|
163
|
+
raw: req,
|
|
164
|
+
});
|
|
165
|
+
res.writeHead(init.status, init.statusText, init.headers).end(body);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
res.writeHead(500).end(err.message);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
app.listen(4000);
|
|
172
|
+
console.log('Listening to port 4000');
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
##### With [`fastify`](https://www.fastify.io/)
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
import Fastify from 'fastify'; // yarn add fastify
|
|
179
|
+
import { createHandler } from 'graphql-http';
|
|
180
|
+
import { schema } from './previous-step';
|
|
181
|
+
|
|
182
|
+
// Create the GraphQL over HTTP handler
|
|
183
|
+
const handler = createHandler({ schema });
|
|
184
|
+
|
|
185
|
+
// Create a fastify instance serving all methods on `/graphql`
|
|
186
|
+
const fastify = Fastify();
|
|
187
|
+
fastify.all('/graphql', async (req, res) => {
|
|
188
|
+
try {
|
|
189
|
+
const [body, init] = await handler({
|
|
190
|
+
url: req.url,
|
|
191
|
+
method: req.method,
|
|
192
|
+
headers: req.headers,
|
|
193
|
+
body: await new Promise((resolve) => {
|
|
194
|
+
let body = '';
|
|
195
|
+
req.on('data', (chunk) => (body += chunk));
|
|
196
|
+
req.on('end', () => resolve(body));
|
|
197
|
+
}),
|
|
198
|
+
raw: req,
|
|
199
|
+
});
|
|
200
|
+
res.writeHead(init.status, init.statusText, init.headers).end(body);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
res.writeHead(500).end(err.message);
|
|
30
203
|
}
|
|
31
|
-
`, { id: 1234 }).then((result) => {
|
|
32
|
-
console.log(result.data.user);
|
|
33
|
-
// => { id: 1234, name: ... }
|
|
34
204
|
});
|
|
205
|
+
|
|
206
|
+
fastify.listen(4000);
|
|
207
|
+
console.log('Listening to port 4000');
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
##### With [`Deno`](https://deno.land/)
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { serve } from 'https://deno.land/std@0.151.0/http/server.ts';
|
|
214
|
+
import { createHandler } from 'https://esm.sh/graphql-http';
|
|
215
|
+
import { schema } from './previous-step';
|
|
216
|
+
|
|
217
|
+
const handler = createHandler<Request>({ schema });
|
|
218
|
+
|
|
219
|
+
await serve(
|
|
220
|
+
async (req: Request) => {
|
|
221
|
+
const [path, _search] = req.url.split('?');
|
|
222
|
+
if (!path.endsWith('/graphql')) {
|
|
223
|
+
return new Response(null, { status: 404, statusText: 'Not Found' });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const headers: Record<string, string> = {};
|
|
227
|
+
req.headers.forEach((value, key) => (headers[key] = value));
|
|
228
|
+
const [body, init] = await handler({
|
|
229
|
+
url: req.url,
|
|
230
|
+
method: req.method,
|
|
231
|
+
headers,
|
|
232
|
+
body: await req.text(),
|
|
233
|
+
raw: req,
|
|
234
|
+
});
|
|
235
|
+
return new Response(body, init);
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
port: 4000,
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// Listening to port 4000
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### Use the client
|
|
246
|
+
|
|
247
|
+
```js
|
|
248
|
+
import { createClient } from 'graphql-http';
|
|
249
|
+
|
|
250
|
+
const client = createClient({
|
|
251
|
+
url: 'http://localhost:4000/graphql',
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
(async () => {
|
|
255
|
+
let cancel = () => {
|
|
256
|
+
/* abort the request if it is in-flight */
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const result = await new Promise((resolve, reject) => {
|
|
260
|
+
let result;
|
|
261
|
+
cancel = client.subscribe(
|
|
262
|
+
{
|
|
263
|
+
query: '{ hello }',
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
next: (data) => (result = data),
|
|
267
|
+
error: reject,
|
|
268
|
+
complete: () => resolve(result),
|
|
269
|
+
},
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
expect(result).toEqual({ hello: 'world' });
|
|
274
|
+
})();
|
|
35
275
|
```
|
|
36
276
|
|
|
37
|
-
|
|
277
|
+
## Recipes
|
|
278
|
+
|
|
279
|
+
<details id="promise">
|
|
280
|
+
<summary><a href="#promise">🔗</a> Client usage with Promise</summary>
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
import { ExecutionResult } from 'graphql';
|
|
284
|
+
import { createClient, RequestParams } from 'graphql-http';
|
|
285
|
+
import { getSession } from './my-auth';
|
|
286
|
+
|
|
287
|
+
const client = createClient({
|
|
288
|
+
url: 'http://hey.there:4000/graphql',
|
|
289
|
+
headers: async () => {
|
|
290
|
+
const session = await getSession();
|
|
291
|
+
if (session) {
|
|
292
|
+
return {
|
|
293
|
+
Authorization: `Bearer ${session.token}`,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
function execute<Data, Extensions>(
|
|
300
|
+
params: RequestParams,
|
|
301
|
+
): [request: Promise<ExecutionResult<Data, Extensions>>, cancel: () => void] {
|
|
302
|
+
let cancel!: () => void;
|
|
303
|
+
const request = new Promise<ExecutionResult<Data, Extensions>>(
|
|
304
|
+
(resolve, reject) => {
|
|
305
|
+
let result: ExecutionResult<Data, Extensions>;
|
|
306
|
+
cancel = client.subscribe<Data, Extensions>(params, {
|
|
307
|
+
next: (data) => (result = data),
|
|
308
|
+
error: reject,
|
|
309
|
+
complete: () => resolve(result),
|
|
310
|
+
});
|
|
311
|
+
},
|
|
312
|
+
);
|
|
313
|
+
return [request, cancel];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
(async () => {
|
|
317
|
+
const [request, cancel] = execute({
|
|
318
|
+
query: '{ hello }',
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// just an example, not a real function
|
|
322
|
+
onUserLeavePage(() => {
|
|
323
|
+
cancel();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const result = await request;
|
|
327
|
+
|
|
328
|
+
expect(result).toBe({ data: { hello: 'world' } });
|
|
329
|
+
})();
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
</details>
|
|
333
|
+
|
|
334
|
+
</details>
|
|
335
|
+
|
|
336
|
+
<details id="observable">
|
|
337
|
+
<summary><a href="#observable">🔗</a> Client usage with <a href="https://github.com/tc39/proposal-observable">Observable</a></summary>
|
|
38
338
|
|
|
39
339
|
```js
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
340
|
+
import { Observable } from 'relay-runtime';
|
|
341
|
+
// or
|
|
342
|
+
import { Observable } from '@apollo/client/core';
|
|
343
|
+
// or
|
|
344
|
+
import { Observable } from 'rxjs';
|
|
345
|
+
// or
|
|
346
|
+
import Observable from 'zen-observable';
|
|
347
|
+
// or any other lib which implements Observables as per the ECMAScript proposal: https://github.com/tc39/proposal-observable
|
|
348
|
+
import { createClient } from 'graphql-http';
|
|
349
|
+
import { getSession } from './my-auth';
|
|
350
|
+
|
|
351
|
+
const client = createClient({
|
|
352
|
+
url: 'http://graphql.loves:4000/observables',
|
|
353
|
+
headers: async () => {
|
|
354
|
+
const session = await getSession();
|
|
355
|
+
if (session) {
|
|
356
|
+
return {
|
|
357
|
+
Authorization: `Bearer ${session.token}`,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const observable = new Observable((observer) =>
|
|
364
|
+
client.subscribe({ query: '{ hello }' }, observer),
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const subscription = observable.subscribe({
|
|
368
|
+
next: (result) => {
|
|
369
|
+
expect(result).toBe({ data: { hello: 'world' } });
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// unsubscribe will cancel the request if it is pending
|
|
374
|
+
subscription.unsubscribe();
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
</details>
|
|
378
|
+
|
|
379
|
+
<details id="relay">
|
|
380
|
+
<summary><a href="#relay">🔗</a> Client usage with <a href="https://relay.dev">Relay</a></summary>
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
import { GraphQLError } from 'graphql';
|
|
384
|
+
import {
|
|
385
|
+
Network,
|
|
386
|
+
Observable,
|
|
387
|
+
RequestParameters,
|
|
388
|
+
Variables,
|
|
389
|
+
} from 'relay-runtime';
|
|
390
|
+
import { createClient } from 'graphql-http';
|
|
391
|
+
import { getSession } from './my-auth';
|
|
392
|
+
|
|
393
|
+
const client = createClient({
|
|
394
|
+
url: 'http://i.love:4000/graphql',
|
|
395
|
+
headers: async () => {
|
|
396
|
+
const session = await getSession();
|
|
397
|
+
if (session) {
|
|
398
|
+
return {
|
|
399
|
+
Authorization: `Bearer ${session.token}`,
|
|
400
|
+
};
|
|
47
401
|
}
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
function fetch(operation: RequestParameters, variables: Variables) {
|
|
406
|
+
return Observable.create((sink) => {
|
|
407
|
+
if (!operation.text) {
|
|
408
|
+
return sink.error(new Error('Operation text cannot be empty'));
|
|
409
|
+
}
|
|
410
|
+
return client.subscribe(
|
|
411
|
+
{
|
|
412
|
+
operationName: operation.name,
|
|
413
|
+
query: operation.text,
|
|
414
|
+
variables,
|
|
415
|
+
},
|
|
416
|
+
sink,
|
|
417
|
+
);
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export const network = Network.create(fetch);
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
</details>
|
|
425
|
+
|
|
426
|
+
<details id="apollo-client">
|
|
427
|
+
<summary><a href="#apollo-client">🔗</a> Client usage with <a href="https://www.apollographql.com">Apollo</a></summary>
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
import {
|
|
431
|
+
ApolloLink,
|
|
432
|
+
Operation,
|
|
433
|
+
FetchResult,
|
|
434
|
+
Observable,
|
|
435
|
+
} from '@apollo/client/core';
|
|
436
|
+
import { print, GraphQLError } from 'graphql';
|
|
437
|
+
import { createClient, ClientOptions, Client } from 'graphql-http';
|
|
438
|
+
import { getSession } from './my-auth';
|
|
439
|
+
|
|
440
|
+
class HTTPLink extends ApolloLink {
|
|
441
|
+
private client: Client;
|
|
442
|
+
|
|
443
|
+
constructor(options: ClientOptions) {
|
|
444
|
+
super();
|
|
445
|
+
this.client = createClient(options);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
public request(operation: Operation): Observable<FetchResult> {
|
|
449
|
+
return new Observable((sink) => {
|
|
450
|
+
return this.client.subscribe<FetchResult>(
|
|
451
|
+
{ ...operation, query: print(operation.query) },
|
|
452
|
+
{
|
|
453
|
+
next: sink.next.bind(sink),
|
|
454
|
+
complete: sink.complete.bind(sink),
|
|
455
|
+
error: sink.error.bind(sink),
|
|
456
|
+
},
|
|
457
|
+
);
|
|
458
|
+
});
|
|
48
459
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const link = new HTTPLink({
|
|
463
|
+
url: 'http://where.is:4000/graphql',
|
|
464
|
+
headers: async () => {
|
|
465
|
+
const session = await getSession();
|
|
466
|
+
if (session) {
|
|
467
|
+
return {
|
|
468
|
+
Authorization: `Bearer ${session.token}`,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
},
|
|
52
472
|
});
|
|
53
473
|
```
|
|
474
|
+
|
|
475
|
+
</details>
|
|
476
|
+
|
|
477
|
+
<details id="request-retries">
|
|
478
|
+
<summary><a href="#request-retries">🔗</a> Client usage with request retries</summary>
|
|
479
|
+
|
|
480
|
+
```ts
|
|
481
|
+
import { createClient, NetworkError } from 'graphql-http';
|
|
482
|
+
|
|
483
|
+
const client = createClient({
|
|
484
|
+
url: 'http://unstable.service:4000/graphql',
|
|
485
|
+
shouldRetry: async (err: NetworkError, retries: number) => {
|
|
486
|
+
if (retries > 3) {
|
|
487
|
+
// max 3 retries and then report service down
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// try again when service unavailable, could be temporary
|
|
492
|
+
if (err.response?.status === 503) {
|
|
493
|
+
// wait one second (you can alternatively time the promise resolution to your preference)
|
|
494
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// otherwise report error immediately
|
|
499
|
+
return false;
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
</details>
|
|
505
|
+
|
|
506
|
+
<details id="browser">
|
|
507
|
+
<summary><a href="#browser">🔗</a> Client usage in browser</summary>
|
|
508
|
+
|
|
509
|
+
```html
|
|
510
|
+
<!DOCTYPE html>
|
|
511
|
+
<html>
|
|
512
|
+
<head>
|
|
513
|
+
<meta charset="utf-8" />
|
|
514
|
+
<title>GraphQL over HTTP</title>
|
|
515
|
+
<script
|
|
516
|
+
type="text/javascript"
|
|
517
|
+
src="https://unpkg.com/graphql-http/umd/graphql-http.min.js"
|
|
518
|
+
></script>
|
|
519
|
+
</head>
|
|
520
|
+
<body>
|
|
521
|
+
<script type="text/javascript">
|
|
522
|
+
const client = graphqlHttp.createClient({
|
|
523
|
+
url: 'http://umdfor.the:4000/win/graphql',
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// consider other recipes for usage inspiration
|
|
527
|
+
</script>
|
|
528
|
+
</body>
|
|
529
|
+
</html>
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
</details>
|
|
533
|
+
|
|
534
|
+
<details id="node-client">
|
|
535
|
+
<summary><a href="#node-client">🔗</a> Client usage in Node</summary>
|
|
536
|
+
|
|
537
|
+
```js
|
|
538
|
+
const fetch = require('node-fetch'); // yarn add node-fetch
|
|
539
|
+
const { AbortController } = require('node-abort-controller'); // (node < v15) yarn add node-abort-controller
|
|
540
|
+
const { createClient } = require('graphql-http');
|
|
541
|
+
|
|
542
|
+
const client = createClient({
|
|
543
|
+
url: 'http://no.browser:4000/graphql',
|
|
544
|
+
fetchFn: fetch,
|
|
545
|
+
abortControllerImpl: AbortController, // node < v15
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
// consider other recipes for usage inspiration
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
</details>
|
|
552
|
+
|
|
553
|
+
<details id="deno-client">
|
|
554
|
+
<summary><a href="#deno-client">🔗</a> Client usage in Deno</summary>
|
|
555
|
+
|
|
556
|
+
```js
|
|
557
|
+
import { createClient } from 'graphql-http';
|
|
558
|
+
|
|
559
|
+
const client = createClient({
|
|
560
|
+
url: 'http://deno.earth:4000/graphql',
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// consider other recipes for usage inspiration
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
</details>
|
|
567
|
+
|
|
568
|
+
<details id="auth">
|
|
569
|
+
<summary><a href="#auth">🔗</a> Server handler usage with authentication</summary>
|
|
570
|
+
|
|
571
|
+
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.
|
|
572
|
+
|
|
573
|
+
```js
|
|
574
|
+
import { createHandler } from 'graphql-http';
|
|
575
|
+
import {
|
|
576
|
+
schema,
|
|
577
|
+
getUserFromCookies,
|
|
578
|
+
getUserFromAuthorizationHeader,
|
|
579
|
+
} from './my-graphql';
|
|
580
|
+
|
|
581
|
+
const handler = createHandler({
|
|
582
|
+
schema,
|
|
583
|
+
context: async (req) => {
|
|
584
|
+
// process token, authenticate user and attach it to your graphql context
|
|
585
|
+
const userId = await getUserFromCookies(req.headers.cookie);
|
|
586
|
+
// or
|
|
587
|
+
const userId = await getUserFromAuthorizationHeader(
|
|
588
|
+
req.headers.authorization,
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
// respond with 401 if the user was not authenticated
|
|
592
|
+
if (!userId) {
|
|
593
|
+
return [null, { status: 401, statusText: 'Unauthorized' }];
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// otherwise attach the user to the graphql context
|
|
597
|
+
return { userId };
|
|
598
|
+
},
|
|
599
|
+
});
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
</details>
|
|
603
|
+
|
|
604
|
+
<details id="context">
|
|
605
|
+
<summary><a href="#context">🔗</a> Server handler usage with custom context value</summary>
|
|
606
|
+
|
|
607
|
+
```js
|
|
608
|
+
import { createHandler } from 'graphql-http';
|
|
609
|
+
import { schema, getDynamicContext } from './my-graphql';
|
|
610
|
+
|
|
611
|
+
const handler = createHandler({
|
|
612
|
+
schema,
|
|
613
|
+
context: async (req, args) => {
|
|
614
|
+
return getDynamicContext(req, args);
|
|
615
|
+
},
|
|
616
|
+
// or static context by supplying the value direcly
|
|
617
|
+
});
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
</details>
|
|
621
|
+
|
|
622
|
+
<details id="custom-exec">
|
|
623
|
+
<summary><a href="#custom-exec">🔗</a> Server handler usage with custom execution arguments</summary>
|
|
624
|
+
|
|
625
|
+
```js
|
|
626
|
+
import { parse } from 'graphql';
|
|
627
|
+
import { createHandler } from 'graphql-http';
|
|
628
|
+
import { getSchemaForRequest, myValidationRules } from './my-graphql';
|
|
629
|
+
|
|
630
|
+
const handler = createHandler({
|
|
631
|
+
onSubscribe: async (req, params) => {
|
|
632
|
+
const schema = await getSchemaForRequest(req);
|
|
633
|
+
|
|
634
|
+
const args = {
|
|
635
|
+
schema,
|
|
636
|
+
operationName: params.operationName,
|
|
637
|
+
document: parse(params.query),
|
|
638
|
+
variableValues: params.variables,
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
return args;
|
|
642
|
+
},
|
|
643
|
+
});
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
</details>
|
|
647
|
+
|
|
648
|
+
<details id="auth-node-early">
|
|
649
|
+
<summary><a href="#auth-node-early">🔗</a> Server handler usage in Node with early authentication (recommended)</summary>
|
|
650
|
+
|
|
651
|
+
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.
|
|
652
|
+
|
|
653
|
+
```js
|
|
654
|
+
import { createHandler } from 'graphql-http';
|
|
655
|
+
import {
|
|
656
|
+
schema,
|
|
657
|
+
getUserFromCookies,
|
|
658
|
+
getUserFromAuthorizationHeader,
|
|
659
|
+
} from './my-graphql';
|
|
660
|
+
|
|
661
|
+
const handler = createHandler({
|
|
662
|
+
schema,
|
|
663
|
+
context: async (req) => {
|
|
664
|
+
// user is authenticated early (see below), simply attach it to the graphql context
|
|
665
|
+
return { userId: req.raw.userId };
|
|
666
|
+
},
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
const server = http.createServer(async (req, res) => {
|
|
670
|
+
if (!req.url.startsWith('/graphql')) {
|
|
671
|
+
return res.writeHead(404).end();
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
try {
|
|
675
|
+
// process token, authenticate user and attach it to the request
|
|
676
|
+
req.userId = await getUserFromCookies(req.headers.cookie);
|
|
677
|
+
// or
|
|
678
|
+
req.userId = await getUserFromAuthorizationHeader(
|
|
679
|
+
req.headers.authorization,
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
// respond with 401 if the user was not authenticated
|
|
683
|
+
if (!req.userId) {
|
|
684
|
+
return res.writeHead(401, 'Unauthorized').end();
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const [body, init] = await handler({
|
|
688
|
+
url: req.url,
|
|
689
|
+
method: req.method,
|
|
690
|
+
headers: req.headers,
|
|
691
|
+
body: await new Promise((resolve) => {
|
|
692
|
+
let body = '';
|
|
693
|
+
req.on('data', (chunk) => (body += chunk));
|
|
694
|
+
req.on('end', () => resolve(body));
|
|
695
|
+
}),
|
|
696
|
+
raw: req,
|
|
697
|
+
});
|
|
698
|
+
res.writeHead(init.status, init.statusText, init.headers).end(body);
|
|
699
|
+
} catch (err) {
|
|
700
|
+
res.writeHead(500).end(err.message);
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
server.listen(4000);
|
|
705
|
+
console.log('Listening to port 4000');
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
</details>
|
|
709
|
+
|
|
710
|
+
## [Documentation](docs/)
|
|
711
|
+
|
|
712
|
+
Check the [docs folder](docs/) out for [TypeDoc](https://typedoc.org) generated documentation.
|
|
713
|
+
|
|
714
|
+
## [Want to help?](CONTRIBUTING.md)
|
|
715
|
+
|
|
716
|
+
File a bug, contribute with code, or improve documentation? Read up on our guidelines for [contributing](CONTRIBUTING.md) and drive development with `yarn test --watch` away!
|