1
1
# node-fetch-server
2
2
3
- Build portable Node.js servers using web-standard Fetch API primitives 🚀
3
+ Build portable Node.js servers using web-standard Fetch API primitives
4
4
5
5
` node-fetch-server ` brings the simplicity and familiarity of the [ Fetch API] ( https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API ) to Node.js server development. Instead of dealing with Node's traditional ` req ` /` res ` objects, you work with web-standard [ ` Request ` ] ( https://developer.mozilla.org/en-US/docs/Web/API/Request ) and [ ` Response ` ] ( https://developer.mozilla.org/en-US/docs/Web/API/Response ) objects—the same APIs you already use in the browser and modern JavaScript runtimes.
6
6
7
7
## Why node-fetch-server?
8
8
9
- - ** Write once, run anywhere** : Your server code becomes portable across Node.js, Deno, Bun, Cloudflare Workers, and other platforms
10
- - ** Familiar API** : Use the same Request/Response APIs you already know from client-side development
11
- - ** Future-proof** : Align with web standards that are here to stay
12
- - ** TypeScript-friendly** : Full type safety with standard web APIs
13
- - ** Lightweight** : Minimal overhead while providing a cleaner, more intuitive API
14
-
15
9
The Fetch API is already the standard for server development in:
16
10
17
11
- [ ` Bun.serve ` ] ( https://bun.sh/docs/api/http#bun-serve )
@@ -40,22 +34,39 @@ npm install @mjackson/node-fetch-server
40
34
41
35
### Basic Server
42
36
37
+ Here's a complete working example with a simple in-memory data store:
38
+
43
39
``` ts
44
40
import * as http from ' node:http' ;
45
41
import { createRequestListener } from ' @mjackson/node-fetch-server' ;
46
42
47
- // Your request handler uses standard Request/Response objects
43
+ // Example: Simple in-memory user storage
44
+ let users = new Map ([
45
+ [
' 1' , { id:
' 1' , name:
' Alice' , email:
' [email protected] ' }],
46
+ [
' 2' , { id:
' 2' , name:
' Bob' , email:
' [email protected] ' }],
47
+ ]);
48
+
48
49
async function handler(request : Request ) {
49
50
let url = new URL (request .url );
50
51
51
- // Route based on pathname
52
- if (url .pathname === ' /' ) {
53
- return new Response (' Welcome to the home page! ' );
52
+ // GET / - Home page
53
+ if (url .pathname === ' /' && request . method === ' GET ' ) {
54
+ return new Response (' Welcome to the User API! Try GET /api/users ' );
54
55
}
55
56
56
- if (url .pathname === ' /api/users' ) {
57
- let users = await getUsers (); // Your async logic here
58
- return Response .json (users );
57
+ // GET /api/users - List all users
58
+ if (url .pathname === ' /api/users' && request .method === ' GET' ) {
59
+ return Response .json (Array .from (users .values ()));
60
+ }
61
+
62
+ // GET /api/users/:id - Get specific user
63
+ let userMatch = url .pathname .match (/ ^ \/ api\/ users\/ (\w + )$ / );
64
+ if (userMatch && request .method === ' GET' ) {
65
+ let user = users .get (userMatch [1 ]);
66
+ if (user ) {
67
+ return Response .json (user );
68
+ }
69
+ return new Response (' User not found' , { status: 404 });
59
70
}
60
71
61
72
return new Response (' Not Found' , { status: 404 });
@@ -71,24 +82,47 @@ server.listen(3000, () => {
71
82
72
83
### Working with Request Data
73
84
85
+ Handle different types of request data using standard web APIs:
86
+
74
87
``` ts
75
88
async function handler(request : Request ) {
76
- // Access request method, headers, and body just like in the browser
77
- if (request .method === ' POST' && request .url .endsWith (' /api/users' )) {
78
- // Parse JSON body
79
- let userData = await request .json ();
80
-
81
- // Validate and process...
82
- let newUser = await createUser (userData );
83
-
84
- // Return JSON response
85
- return Response .json (newUser , {
86
- status: 201 ,
87
- headers: { ' Content-Type' : ' application/json' },
89
+ let url = new URL (request .url );
90
+
91
+ // Handle JSON data
92
+ if (request .method === ' POST' && url .pathname === ' /api/users' ) {
93
+ try {
94
+ let userData = await request .json ();
95
+
96
+ // Validate required fields
97
+ if (! userData .name || ! userData .email ) {
98
+ return Response .json ({ error: ' Name and email are required' }, { status: 400 });
99
+ }
100
+
101
+ // Create user (your implementation)
102
+ let newUser = {
103
+ id: Date .now ().toString (),
104
+ ... userData ,
105
+ };
106
+
107
+ return Response .json (newUser , { status: 201 });
108
+ } catch (error ) {
109
+ return Response .json ({ error: ' Invalid JSON' }, { status: 400 });
110
+ }
111
+ }
112
+
113
+ // Handle URL search params
114
+ if (url .pathname === ' /api/search' ) {
115
+ let query = url .searchParams .get (' q' );
116
+ let limit = parseInt (url .searchParams .get (' limit' ) || ' 10' );
117
+
118
+ return Response .json ({
119
+ query ,
120
+ limit ,
121
+ results: [], // Your search results here
88
122
});
89
123
}
90
124
91
- return new Response (' Method not allowed ' , { status: 405 });
125
+ return new Response (' Not Found ' , { status: 404 });
92
126
}
93
127
```
94
128
@@ -204,10 +238,17 @@ let server = http.createServer(async (req, res) => {
204
238
let request = createRequest (req , res , { host: process .env .HOST });
205
239
206
240
try {
207
- // Your custom middleware pipeline
208
- let response = await pipeline ( authenticate , authorize , handleRequest )( request );
241
+ // Add custom headers or middleware logic
242
+ let startTime = Date . now ( );
209
243
210
- // Convert Fetch API Response back to Node.js response
244
+ // Process the request with your handler
245
+ let response = await handler (request );
246
+
247
+ // Add response timing header
248
+ let duration = Date .now () - startTime ;
249
+ response .headers .set (' X-Response-Time' , ` ${duration }ms ` );
250
+
251
+ // Send the response
211
252
await sendResponse (res , response );
212
253
} catch (error ) {
213
254
console .error (' Server error:' , error );
@@ -233,42 +274,48 @@ This is useful for:
233
274
234
275
## Migration from Express
235
276
236
- Transitioning from Express? Here's a quick comparison:
277
+ Transitioning from Express? Here's a comparison of common patterns:
278
+
279
+ ### Basic Routing
237
280
238
281
``` ts
239
- // Express way
282
+ // Express
283
+ let app = express ();
284
+
240
285
app .get (' /users/:id' , async (req , res ) => {
241
- let user = await getUser (req .params .id );
286
+ let user = await db .getUser (req .params .id );
287
+ if (! user ) {
288
+ return res .status (404 ).json ({ error: ' User not found' });
289
+ }
242
290
res .json (user );
243
291
});
244
292
245
- // node-fetch-server way
293
+ app .listen (3000 );
294
+
295
+ // node-fetch-server
296
+ import { createRequestListener } from ' @mjackson/node-fetch-server' ;
297
+
246
298
async function handler(request : Request ) {
247
299
let url = new URL (request .url );
248
300
let match = url .pathname .match (/ ^ \/ users\/ (\w + )$ / );
249
301
250
- if (match ) {
251
- let user = await getUser (match [1 ]);
302
+ if (match && request .method === ' GET' ) {
303
+ let user = await db .getUser (match [1 ]);
304
+ if (! user ) {
305
+ return Response .json ({ error: ' User not found' }, { status: 404 });
306
+ }
252
307
return Response .json (user );
253
308
}
254
309
255
310
return new Response (' Not Found' , { status: 404 });
256
311
}
257
- ```
258
312
259
- Common patterns:
260
-
261
- - ` req.body ` → ` await request.json() `
262
- - ` req.params ` → Parse from ` new URL(request.url).pathname `
263
- - ` req.query ` → ` new URL(request.url).searchParams `
264
- - ` res.json(data) ` → ` Response.json(data) `
265
- - ` res.status(404).send() ` → ` new Response('', { status: 404 }) `
266
- - ` res.redirect() ` → ` new Response(null, { status: 302, headers: { Location: '/path' } }) `
313
+ http .createServer (createRequestListener (handler )).listen (3000 );
314
+ ```
267
315
268
316
## Related Packages
269
317
270
318
- [ ` fetch-proxy ` ] ( https://github.com/mjackson/remix-the-web/tree/main/packages/fetch-proxy ) - Build HTTP proxy servers using the web fetch API
271
- - [ ` fetch-router ` ] ( https://github.com/mjackson/remix-the-web/tree/main/packages/fetch-router ) - URL pattern routing for Fetch API servers
272
319
273
320
## License
274
321
0 commit comments