@whatwg-node/server 0.0.2 → 0.0.5

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/README.md ADDED
@@ -0,0 +1,290 @@
1
+ # WHATWG Node Generic Server Adapter
2
+
3
+ `@whatwg-node/server` helps you to create a generic server implementation by using WHATWG Fetch API for Node.js, AWS Lambda, Cloudflare Workers, Deno, Express, Fastify, Koa, Next.js and Sveltekit.
4
+
5
+ Once you create an adapter with `createServerAdapter`, you don't need to install any other platform specific package since the generic adapter will handle it automatically.
6
+
7
+ ## How to start
8
+
9
+ Let's create a basic Hello World server adapter.
10
+
11
+ ```ts
12
+ // myServerAdapter.ts
13
+ import { createServerAdapter } from '@whatwg-node/server'
14
+
15
+ export default createServerAdapter({
16
+ handleRequest(request: Request) {
17
+ return new Response(`Hello World!`, { status: 200 })
18
+ }
19
+ })
20
+ ```
21
+
22
+ ## Integrations
23
+
24
+ You can use your server adapter with the following integrations:
25
+
26
+ ### Node.js
27
+
28
+ [Node.js](https://nodejs.org/api/http.html) is the most popular server side JavaScript runtime.
29
+
30
+ ```ts
31
+ import myServerAdapter from './myServerAdapter'
32
+ import { createServer } from 'http'
33
+
34
+ // You can create your Node server instance by using our adapter
35
+ const nodeServer = createServer(myServerAdapter)
36
+ // Then start listening on some port
37
+ nodeServer.listen(4000)
38
+ ```
39
+
40
+ ### AWS Lambda
41
+
42
+ AWS Lambda is a serverless computing platform that makes it easy to build applications that run on the AWS cloud. Our adaoter is platform agnostic so they can fit together easily. In order to reduce the boilerplate we prefer to use [Serverless Express from Vendia](https://github.com/vendia/serverless-express).
43
+
44
+ ```ts
45
+ import myServerAdapter from './myServerAdapter'
46
+ import type { Handler } from '@aws-cdk/aws-lambda'
47
+ import { configure } from '@vendia/serverless-express'
48
+
49
+ export const handler: Handler = configure({
50
+ app: myServerAdapter
51
+ })
52
+ ```
53
+
54
+ ### Cloudflare Workers
55
+
56
+ Cloudflare Workers provides a serverless execution environment that allows you to create entirely new applications or augment existing ones without configuring or maintaining infrastructure. It uses Fetch API already so we can use our adapter as an event listener like below;
57
+
58
+ ```ts
59
+ import myServerAdapter from './myServerAdapter'
60
+
61
+ self.addEventListener('fetch', myServerAdapter)
62
+ ```
63
+
64
+ ### Deno
65
+
66
+ [Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust](https://deno.land/).
67
+ You can use our adapter as a Deno request handler like below;
68
+
69
+ ```ts
70
+ import { serve } from 'https://deno.land/std@0.117.0/http/server.ts'
71
+ import myServerAdapter from './myServerAdapter'
72
+
73
+ serve(myServerAdapter, {
74
+ // Listen any port you want
75
+ addr: ':4000'
76
+ })
77
+ ```
78
+
79
+ ### Express
80
+
81
+ [Express is the most popular web framework for Node.js.](https://expressjs.com/) It is a minimalist framework that provides a robust set of features to handle HTTP on Node.js applications.
82
+
83
+ You can easily integrate your adapter into your Express application with a few lines of code.
84
+
85
+ ```ts
86
+ import express from 'express'
87
+ import myServerAdapter from './myServerAdapter'
88
+
89
+ const app = express()
90
+
91
+ // Bind our adapter to `/mypath` endpoint
92
+ app.use('/mypath', myServerAdapter)
93
+
94
+ app.listen(4000, () => {
95
+ console.log('Running the server at http://localhost:4000/mypath')
96
+ })
97
+ ```
98
+
99
+ ### Fastify
100
+
101
+ [Fastify is one of the popular HTTP server frameworks for Node.js.](https://www.fastify.io/). You can use your adapter easily with Fastify.
102
+
103
+ So you can benefit from the powerful plugins of Fastify ecosystem.
104
+ [See the ecosystem](https://www.fastify.io/docs/latest/Guides/Ecosystem/)
105
+
106
+ ```ts
107
+ import myServerAdapter from './myServerAdapter'
108
+ import fastify, { FastifyRequest, FastifyReply } from 'fastify'
109
+
110
+ // This is the fastify instance you have created
111
+ const app = fastify({ logger: true })
112
+
113
+ /**
114
+ * We pass the incoming HTTP request to our adapter
115
+ * and handle the response using Fastify's `reply` API
116
+ * Learn more about `reply` https://www.fastify.io/docs/latest/Reply/
117
+ **/
118
+ app.route({
119
+ url: '/mypath',
120
+ method: ['GET', 'POST', 'OPTIONS'],
121
+ handler: async (req, reply) => {
122
+ const response = await myServerAdapter.handleNodeRequest(req)
123
+ response.headers.forEach((value, key) => {
124
+ reply.header(key, value)
125
+ })
126
+
127
+ reply.status(response.status)
128
+
129
+ reply.send(response.body)
130
+
131
+ return reply
132
+ }
133
+ })
134
+
135
+ app.listen(4000)
136
+ ```
137
+
138
+ ### Koa
139
+
140
+ [Koa is another Node.js server framework designed by the team behind Express, which aims to be a smaller, more expressive.](https://koajs.com/) You can add your adapter to your Koa application with a few lines of code then [benefit middlewares written for Koa.](https://github.com/koajs/koa/wiki)
141
+
142
+ ```ts
143
+ import Koa from 'koa'
144
+ import myServerAdapter from './myServerAdapter'
145
+
146
+ const app = new Koa()
147
+
148
+ app.use(async ctx => {
149
+ const response = await myServerAdapter.handleNodeRequest(ctx.req)
150
+
151
+ // Set status code
152
+ ctx.status = response.status
153
+
154
+ // Set headers
155
+ response.headers.forEach((value, key) => {
156
+ ctx.append(key, value)
157
+ })
158
+
159
+ ctx.body = response.body
160
+ })
161
+
162
+ app.listen(4000, () => {
163
+ console.log('Running the server at http://localhost:4000')
164
+ })
165
+ ```
166
+
167
+ ### Next.js
168
+
169
+ [Next.js](https://nextjs.org/) is a web framework that allows you to build websites very quickly and our new server adapter can be integrated with Next.js easily as an API Route.
170
+
171
+ ```ts
172
+ // pages/api/myEndpoint.ts
173
+ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
174
+ import myServerAdapter from './myServerAdapter'
175
+ import type { NextApiRequest, NextApiResponse } from 'next'
176
+
177
+ export const config = {
178
+ api: {
179
+ // Disable body parsing if you expect a request other than JSON
180
+ bodyParser: false
181
+ }
182
+ }
183
+
184
+ export default myServerAdapter
185
+ ```
186
+
187
+ ### SvelteKit
188
+
189
+ [SvelteKit](https://kit.svelte.dev/) is the fastest way to build svelte apps. It is very simple, and let you build frontend & backend in a single place
190
+
191
+ ```ts
192
+ import myServerAdapter from './myServerAdapter'
193
+
194
+ export { myServerAdapter as get, myServerAdapter as post }
195
+ ```
196
+
197
+ ## File Uploads / Multipart Requests
198
+
199
+ Multipart requests are a type of HTTP request that allows you to send blobs together with regular text data which has a mime-type `multipart/form-data`.
200
+
201
+ For example, if you send a multipart request from a browser with `FormData`, you can get the same `FormData` object in your request handler.
202
+
203
+ ```ts
204
+ import { createServerAdapter } from '@whatwg-node/server'
205
+
206
+ const myServerAdapter = createServerAdapter({
207
+ // ...
208
+ async handleRequest(request) {
209
+ // Parse the request as `FormData`
210
+ const formData = await request.formData()
211
+ // Select the file
212
+ const file = formData.get('file')
213
+ // Process it as a string
214
+ const fileTextContent = await file.text()
215
+ // Select the other text parameter
216
+ const regularTextData = formData.get('additionalStuff')
217
+ // ...
218
+ return new Response('{ "message": "ok" }', {
219
+ status: 200,
220
+ headers: {
221
+ 'Content-Type': 'application/json'
222
+ }
223
+ })
224
+ }
225
+ })
226
+ ```
227
+
228
+ You can learn more about [File API](https://developer.mozilla.org/en-US/docs/Web/API/File) on MDN documentation.
229
+
230
+ ## Routing and Middlewares
231
+
232
+ We'd recommend to use [itty-router](https://github.com/kwhitley/itty-router) to handle routing and middleware approach. So it is really easy to integrate your router to `@whatwg-node/server`.
233
+
234
+ ### Basic Routing
235
+
236
+ ```ts
237
+ import { Router } from 'itty-router'
238
+ import { createServerAdapter } from '@whatwg-node/server'
239
+ // now let's create a router (note the lack of "new")
240
+ const router = Router()
241
+ // GET collection index
242
+ router.get('/todos', () => new Response('Todos Index!'))
243
+ // GET item
244
+ router.get('/todos/:id', ({ params }) => new Response(`Todo #${params.id}`))
245
+ // POST to the collection (we'll use async here)
246
+ router.post('/todos', async request => {
247
+ const content = await request.json()
248
+ return new Response('Creating Todo: ' + JSON.stringify(content))
249
+ })
250
+ // 404 for everything else
251
+ router.all('*', () => new Response('Not Found.', { status: 404 }))
252
+ // attach the router "handle" to our server adapter
253
+ const myServerAdapter = createServerAdapter({
254
+ handleRequest: router.handle
255
+ })
256
+
257
+ // Then use it in any environment
258
+ import { createServer } from 'http'
259
+ const httpServer = createServer(myServer)
260
+ httpServer.listen(4000)
261
+ ```
262
+
263
+ ### Middlewares to handle CORS, cookies and more
264
+
265
+ There is another package called [itty-router-extras](https://www.npmjs.com/package/itty-router-extras) that provides some utilities for your platform agnostic server implementation. The following example shows how to get the cookies as an object from the request.
266
+
267
+ ```ts
268
+ import { withCookies } from 'itty-router-extras'
269
+
270
+ router.get('/foo', withCookies, ({ cookies }) => {
271
+ // cookies are parsed from the header into request.cookies
272
+ return new Response(`Cookies: ${JSON.stringify(cookies)}`)
273
+ })
274
+ ```
275
+
276
+ You can also setup a CORS middleware to handle preflight CORS requests.
277
+
278
+ ```ts
279
+ import { withCors } from 'itty-router-extras'
280
+
281
+ router.all(
282
+ '*',
283
+ withCors({
284
+ origin: 'http://localhost:4000',
285
+ methods: 'GET, POST, PATCH, DELETE',
286
+ headers: 'authorization, referer, origin, content-type',
287
+ credentials: false
288
+ })
289
+ )
290
+ ```
package/index.js CHANGED
@@ -15,7 +15,7 @@ function buildFullUrl(nodeRequest) {
15
15
  'localhost';
16
16
  const port = ((_h = nodeRequest.socket) === null || _h === void 0 ? void 0 : _h.localPort) || 80;
17
17
  const protocol = nodeRequest.protocol || 'http';
18
- const endpoint = nodeRequest.url || '/graphql';
18
+ const endpoint = nodeRequest.originalUrl || nodeRequest.url || '/graphql';
19
19
  return `${protocol}://${hostname}:${port}${endpoint}`;
20
20
  }
21
21
  function configureSocket(rawRequest) {
@@ -40,13 +40,14 @@ function normalizeNodeRequest(nodeRequest, RequestCtor) {
40
40
  var _a;
41
41
  const rawRequest = nodeRequest.raw || nodeRequest.req || nodeRequest;
42
42
  configureSocket(rawRequest);
43
- const fullUrl = buildFullUrl(rawRequest);
43
+ let fullUrl = buildFullUrl(rawRequest);
44
44
  if (nodeRequest.query) {
45
45
  const urlObj = new URL(fullUrl);
46
46
  for (const queryName in nodeRequest.query) {
47
47
  const queryValue = nodeRequest.query[queryName];
48
48
  urlObj.searchParams.set(queryName, queryValue);
49
49
  }
50
+ fullUrl = urlObj.toString();
50
51
  }
51
52
  const baseRequestInit = {
52
53
  method: nodeRequest.method,
package/index.mjs CHANGED
@@ -11,7 +11,7 @@ function buildFullUrl(nodeRequest) {
11
11
  'localhost';
12
12
  const port = ((_h = nodeRequest.socket) === null || _h === void 0 ? void 0 : _h.localPort) || 80;
13
13
  const protocol = nodeRequest.protocol || 'http';
14
- const endpoint = nodeRequest.url || '/graphql';
14
+ const endpoint = nodeRequest.originalUrl || nodeRequest.url || '/graphql';
15
15
  return `${protocol}://${hostname}:${port}${endpoint}`;
16
16
  }
17
17
  function configureSocket(rawRequest) {
@@ -36,13 +36,14 @@ function normalizeNodeRequest(nodeRequest, RequestCtor) {
36
36
  var _a;
37
37
  const rawRequest = nodeRequest.raw || nodeRequest.req || nodeRequest;
38
38
  configureSocket(rawRequest);
39
- const fullUrl = buildFullUrl(rawRequest);
39
+ let fullUrl = buildFullUrl(rawRequest);
40
40
  if (nodeRequest.query) {
41
41
  const urlObj = new URL(fullUrl);
42
42
  for (const queryName in nodeRequest.query) {
43
43
  const queryValue = nodeRequest.query[queryName];
44
44
  urlObj.searchParams.set(queryName, queryValue);
45
45
  }
46
+ fullUrl = urlObj.toString();
46
47
  }
47
48
  const baseRequestInit = {
48
49
  method: nodeRequest.method,
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@whatwg-node/server",
3
- "version": "0.0.2",
3
+ "version": "0.0.5",
4
4
  "description": "Fetch API compliant HTTP Server adapter",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
7
7
  "@types/node": "^18.0.6"
8
8
  },
9
9
  "dependencies": {
10
- "@whatwg-node/fetch": "^0.0.2",
10
+ "@whatwg-node/fetch": "^0.2.0",
11
11
  "tslib": "^2.3.1"
12
12
  },
13
13
  "repository": {
package/utils.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ /// <reference types="node" />
2
4
  import type { IncomingMessage, ServerResponse } from 'node:http';
3
5
  import type { Socket } from 'node:net';
4
6
  import type { Readable } from 'node:stream';
@@ -7,6 +9,7 @@ export interface NodeRequest {
7
9
  hostname?: string;
8
10
  body?: any;
9
11
  url?: string;
12
+ originalUrl?: string;
10
13
  method?: string;
11
14
  headers: any;
12
15
  req?: IncomingMessage;