bun-types 1.2.3-canary.20250216T140609 → 1.2.3-canary.20250218T140705
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/bun.d.ts +229 -187
- package/docs/api/fetch.md +1 -1
- package/docs/api/http.md +373 -81
- package/docs/api/spawn.md +1 -1
- package/docs/api/sql.md +20 -10
- package/docs/bundler/fullstack.md +22 -14
- package/docs/cli/publish.md +1 -1
- package/docs/guides/ecosystem/nuxt.md +1 -1
- package/docs/guides/ecosystem/render.md +1 -1
- package/docs/guides/html-rewriter/extract-links.md +68 -0
- package/docs/guides/html-rewriter/extract-social-meta.md +93 -0
- package/docs/guides/install/add-peer.md +2 -2
- package/docs/guides/install/from-npm-install-to-bun-install.md +1 -1
- package/docs/guides/test/run-tests.md +3 -3
- package/docs/guides/test/snapshot.md +3 -3
- package/docs/guides/test/update-snapshots.md +1 -1
- package/docs/guides/util/version.md +1 -1
- package/docs/installation.md +8 -6
- package/docs/runtime/debugger.md +3 -3
- package/docs/test/dom.md +1 -1
- package/package.json +1 -1
package/docs/api/http.md
CHANGED
|
@@ -8,130 +8,158 @@ To start a high-performance HTTP server with a clean API, the recommended approa
|
|
|
8
8
|
|
|
9
9
|
## `Bun.serve()`
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Use `Bun.serve` to start an HTTP server in Bun.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
14
|
Bun.serve({
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
// `routes` requires Bun v1.2.3+
|
|
16
|
+
routes: {
|
|
17
|
+
// Static routes
|
|
18
|
+
"/api/status": new Response("OK"),
|
|
19
|
+
|
|
20
|
+
// Dynamic routes
|
|
21
|
+
"/users/:id": req => {
|
|
22
|
+
return new Response(`Hello User ${req.params.id}!`);
|
|
23
|
+
},
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
// Per-HTTP method handlers
|
|
26
|
+
"/api/posts": {
|
|
27
|
+
GET: () => new Response("List posts"),
|
|
28
|
+
POST: async req => {
|
|
29
|
+
const body = await req.json();
|
|
30
|
+
return Response.json({ created: true, ...body });
|
|
31
|
+
},
|
|
32
|
+
},
|
|
22
33
|
|
|
23
|
-
|
|
34
|
+
// Wildcard route for all routes that start with "/api/" and aren't otherwise matched
|
|
35
|
+
"/api/*": Response.json({ message: "Not found" }, { status: 404 }),
|
|
24
36
|
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
// Redirect from /blog/hello to /blog/hello/world
|
|
38
|
+
"/blog/hello": Response.redirect("/blog/hello/world"),
|
|
39
|
+
|
|
40
|
+
// Serve a file by buffering it in memory
|
|
41
|
+
"/favicon.ico": new Response(await Bun.file("./favicon.ico").bytes(), {
|
|
42
|
+
headers: {
|
|
43
|
+
"Content-Type": "image/x-icon",
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// (optional) fallback for unmatched routes:
|
|
49
|
+
// Required if Bun's version < 1.2.3
|
|
27
50
|
fetch(req) {
|
|
28
|
-
|
|
29
|
-
if (url.pathname === "/") return new Response("Home page!");
|
|
30
|
-
if (url.pathname === "/blog") return new Response("Blog!");
|
|
31
|
-
return new Response("404!");
|
|
51
|
+
return new Response("Not Found", { status: 404 });
|
|
32
52
|
},
|
|
33
53
|
});
|
|
34
54
|
```
|
|
35
55
|
|
|
36
|
-
|
|
56
|
+
### Routing
|
|
57
|
+
|
|
58
|
+
Routes in `Bun.serve()` receive a `BunRequest` (which extends [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)) and return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) or `Promise<Response>`. This makes it easier to use the same code for both sending & receiving HTTP requests.
|
|
37
59
|
|
|
38
60
|
```ts
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
await sleep(10);
|
|
44
|
-
const end = performance.now();
|
|
45
|
-
return new Response(`Slept for ${end - start}ms`);
|
|
46
|
-
},
|
|
47
|
-
});
|
|
61
|
+
// Simplified for brevity
|
|
62
|
+
interface BunRequest<T extends string> extends Request {
|
|
63
|
+
params: Record<T, string>;
|
|
64
|
+
}
|
|
48
65
|
```
|
|
49
66
|
|
|
50
|
-
|
|
67
|
+
#### Async/await in routes
|
|
68
|
+
|
|
69
|
+
You can use async/await in route handlers to return a `Promise<Response>`.
|
|
51
70
|
|
|
52
71
|
```ts
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
72
|
+
import { sql, serve } from "bun";
|
|
73
|
+
|
|
74
|
+
serve({
|
|
75
|
+
port: 3001,
|
|
76
|
+
routes: {
|
|
77
|
+
"/api/version": async () => {
|
|
78
|
+
const [version] = await sql`SELECT version()`;
|
|
79
|
+
return Response.json(version);
|
|
80
|
+
},
|
|
57
81
|
},
|
|
58
82
|
});
|
|
59
83
|
```
|
|
60
84
|
|
|
61
|
-
|
|
85
|
+
#### Promise in routes
|
|
86
|
+
|
|
87
|
+
You can also return a `Promise<Response>` from a route handler.
|
|
62
88
|
|
|
63
89
|
```ts
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
90
|
+
import { sql, serve } from "bun";
|
|
91
|
+
|
|
92
|
+
serve({
|
|
93
|
+
routes: {
|
|
94
|
+
"/api/version": () => {
|
|
95
|
+
return new Promise(resolve => {
|
|
96
|
+
setTimeout(async () => {
|
|
97
|
+
const [version] = await sql`SELECT version()`;
|
|
98
|
+
resolve(Response.json(version));
|
|
99
|
+
}, 100);
|
|
100
|
+
});
|
|
101
|
+
},
|
|
69
102
|
},
|
|
70
103
|
});
|
|
71
104
|
```
|
|
72
105
|
|
|
73
|
-
|
|
106
|
+
#### Type-safe route parameters
|
|
74
107
|
|
|
75
|
-
|
|
108
|
+
TypeScript parses route parameters when passed as a string literal, so that your editor will show autocomplete when accessing `request.params`.
|
|
76
109
|
|
|
77
110
|
```ts
|
|
78
|
-
|
|
79
|
-
Bun.serve({
|
|
80
|
-
static: {
|
|
81
|
-
// health-check endpoint
|
|
82
|
-
"/api/health-check": new Response("All good!"),
|
|
111
|
+
import type { BunRequest } from "bun";
|
|
83
112
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"/index.html": new Response(await Bun.file("./index.html").bytes(), {
|
|
92
|
-
headers: {
|
|
93
|
-
"Content-Type": "text/html",
|
|
94
|
-
},
|
|
95
|
-
}),
|
|
96
|
-
"/favicon.ico": new Response(await Bun.file("./favicon.ico").bytes(), {
|
|
97
|
-
headers: {
|
|
98
|
-
"Content-Type": "image/x-icon",
|
|
99
|
-
},
|
|
100
|
-
}),
|
|
101
|
-
|
|
102
|
-
// serve JSON
|
|
103
|
-
"/api/version.json": Response.json({ version: "1.0.0" }),
|
|
104
|
-
},
|
|
113
|
+
Bun.serve({
|
|
114
|
+
routes: {
|
|
115
|
+
// TypeScript knows the shape of params when passed as a string literal
|
|
116
|
+
"/orgs/:orgId/repos/:repoId": req => {
|
|
117
|
+
const { orgId, repoId } = req.params;
|
|
118
|
+
return Response.json({ orgId, repoId });
|
|
119
|
+
},
|
|
105
120
|
|
|
106
|
-
|
|
107
|
-
|
|
121
|
+
"/orgs/:orgId/repos/:repoId/settings": (
|
|
122
|
+
// optional: you can explicitly pass a type to BunRequest:
|
|
123
|
+
req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
|
|
124
|
+
) => {
|
|
125
|
+
const { orgId, repoId } = req.params;
|
|
126
|
+
return Response.json({ orgId, repoId });
|
|
127
|
+
},
|
|
108
128
|
},
|
|
109
129
|
});
|
|
110
130
|
```
|
|
111
131
|
|
|
112
|
-
|
|
132
|
+
Percent-encoded route parameter values are automatically decoded. Unicode characters are supported. Invalid unicode is replaced with the unicode replacement character `&0xFFFD;`.
|
|
133
|
+
|
|
134
|
+
### Static responses
|
|
135
|
+
|
|
136
|
+
Routes can also be `Response` objects (without the handler function). Bun.serve() optimizes it for zero-allocation dispatch - perfect for health checks, redirects, and fixed content:
|
|
113
137
|
|
|
114
138
|
```ts
|
|
115
139
|
Bun.serve({
|
|
116
|
-
|
|
117
|
-
|
|
140
|
+
routes: {
|
|
141
|
+
// Health checks
|
|
142
|
+
"/health": new Response("OK"),
|
|
143
|
+
"/ready": new Response("Ready", {
|
|
118
144
|
headers: {
|
|
119
|
-
|
|
145
|
+
// Pass custom headers
|
|
146
|
+
"X-Ready": "1",
|
|
120
147
|
},
|
|
121
148
|
}),
|
|
122
|
-
},
|
|
123
149
|
|
|
124
|
-
|
|
125
|
-
|
|
150
|
+
// Redirects
|
|
151
|
+
"/blog": Response.redirect("https://bun.sh/blog"),
|
|
152
|
+
|
|
153
|
+
// API responses
|
|
154
|
+
"/api/config": Response.json({
|
|
155
|
+
version: "1.0.0",
|
|
156
|
+
env: "production",
|
|
157
|
+
}),
|
|
126
158
|
},
|
|
127
159
|
});
|
|
128
160
|
```
|
|
129
161
|
|
|
130
|
-
Static
|
|
131
|
-
|
|
132
|
-
{% note %}
|
|
133
|
-
`static` is experimental
|
|
134
|
-
{% /note %}
|
|
162
|
+
Static responses do not allocate additional memory after initialization. You can generally expect at least a 15% performance improvement over manually returning a `Response` object.
|
|
135
163
|
|
|
136
164
|
Static route responses are cached for the lifetime of the server object. To reload static routes, call `server.reload(options)`.
|
|
137
165
|
|
|
@@ -160,7 +188,7 @@ setInterval(() => {
|
|
|
160
188
|
}, 1000);
|
|
161
189
|
```
|
|
162
190
|
|
|
163
|
-
Reloading
|
|
191
|
+
Reloading routes only impact the next request. In-flight requests continue to use the old routes. After in-flight requests to old routes are finished, the old routes are freed from memory.
|
|
164
192
|
|
|
165
193
|
To simplify error handling, static routes do not support streaming response bodies from `ReadableStream` or an `AsyncIterator`. Fortunately, you can still buffer the response in memory first:
|
|
166
194
|
|
|
@@ -180,6 +208,270 @@ const server = Bun.serve({
|
|
|
180
208
|
});
|
|
181
209
|
```
|
|
182
210
|
|
|
211
|
+
### Route precedence
|
|
212
|
+
|
|
213
|
+
Routes are matched in order of specificity:
|
|
214
|
+
|
|
215
|
+
1. Exact routes (`/users/all`)
|
|
216
|
+
2. Parameter routes (`/users/:id`)
|
|
217
|
+
3. Wildcard routes (`/users/*`)
|
|
218
|
+
4. Global catch-all (`/*`)
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
Bun.serve({
|
|
222
|
+
routes: {
|
|
223
|
+
// Most specific first
|
|
224
|
+
"/api/users/me": () => new Response("Current user"),
|
|
225
|
+
"/api/users/:id": req => new Response(`User ${req.params.id}`),
|
|
226
|
+
"/api/*": () => new Response("API catch-all"),
|
|
227
|
+
"/*": () => new Response("Global catch-all"),
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Per-HTTP Method Routes
|
|
233
|
+
|
|
234
|
+
Route handlers can be specialized by HTTP method:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
Bun.serve({
|
|
238
|
+
routes: {
|
|
239
|
+
"/api/posts": {
|
|
240
|
+
// Different handlers per method
|
|
241
|
+
GET: () => new Response("List posts"),
|
|
242
|
+
POST: async req => {
|
|
243
|
+
const post = await req.json();
|
|
244
|
+
return Response.json({ id: crypto.randomUUID(), ...post });
|
|
245
|
+
},
|
|
246
|
+
PUT: async req => {
|
|
247
|
+
const updates = await req.json();
|
|
248
|
+
return Response.json({ updated: true, ...updates });
|
|
249
|
+
},
|
|
250
|
+
DELETE: () => new Response(null, { status: 204 }),
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
You can pass any of the following methods:
|
|
257
|
+
|
|
258
|
+
| Method | Usecase example |
|
|
259
|
+
| --------- | ------------------------------- |
|
|
260
|
+
| `GET` | Fetch a resource |
|
|
261
|
+
| `HEAD` | Check if a resource exists |
|
|
262
|
+
| `OPTIONS` | Get allowed HTTP methods (CORS) |
|
|
263
|
+
| `DELETE` | Delete a resource |
|
|
264
|
+
| `PATCH` | Update a resource |
|
|
265
|
+
| `POST` | Create a resource |
|
|
266
|
+
| `PUT` | Update a resource |
|
|
267
|
+
|
|
268
|
+
When passing a function instead of an object, all methods will be handled by that function:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
const server = Bun.serve({
|
|
272
|
+
routes: {
|
|
273
|
+
"/api/version": () => Response.json({ version: "1.0.0" }),
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await fetch(new URL("/api/version", server.url));
|
|
278
|
+
await fetch(new URL("/api/version", server.url), { method: "PUT" });
|
|
279
|
+
// ... etc
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Hot Route Reloading
|
|
283
|
+
|
|
284
|
+
Update routes without server restarts using `server.reload()`:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
const server = Bun.serve({
|
|
288
|
+
routes: {
|
|
289
|
+
"/api/version": () => Response.json({ version: "1.0.0" }),
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Deploy new routes without downtime
|
|
294
|
+
server.reload({
|
|
295
|
+
routes: {
|
|
296
|
+
"/api/version": () => Response.json({ version: "2.0.0" }),
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Error Handling
|
|
302
|
+
|
|
303
|
+
Bun provides structured error handling for routes:
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
Bun.serve({
|
|
307
|
+
routes: {
|
|
308
|
+
// Errors are caught automatically
|
|
309
|
+
"/api/risky": () => {
|
|
310
|
+
throw new Error("Something went wrong");
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
// Global error handler
|
|
314
|
+
error(error) {
|
|
315
|
+
console.error(error);
|
|
316
|
+
return new Response(`Internal Error: ${error.message}`, {
|
|
317
|
+
status: 500,
|
|
318
|
+
headers: {
|
|
319
|
+
"Content-Type": "text/plain",
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### HTML imports
|
|
327
|
+
|
|
328
|
+
To add a client-side single-page app, you can use an HTML import:
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
import myReactSinglePageApp from "./index.html";
|
|
332
|
+
|
|
333
|
+
Bun.serve({
|
|
334
|
+
routes: {
|
|
335
|
+
"/": myReactSinglePageApp,
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
HTML imports don't just serve HTML. It's a full-featured frontend bundler, transpiler, and toolkit built using Bun's [bundler](https://bun.sh/docs/bundler), JavaScript transpiler and CSS parser.
|
|
341
|
+
|
|
342
|
+
You can use this to build a full-featured frontend with React, TypeScript, Tailwind CSS, and more. Check out [/docs/bundler/fullstack](https://bun.sh/docs/bundler/fullstack) to learn more.
|
|
343
|
+
|
|
344
|
+
### Practical example: REST API
|
|
345
|
+
|
|
346
|
+
Here's a basic database-backed REST API using Bun's router with zero dependencies:
|
|
347
|
+
|
|
348
|
+
{% codetabs %}
|
|
349
|
+
|
|
350
|
+
```ts#server.ts
|
|
351
|
+
import type { Post } from "./types.ts";
|
|
352
|
+
import { Database } from "bun:sqlite";
|
|
353
|
+
|
|
354
|
+
const db = new Database("posts.db");
|
|
355
|
+
db.exec(`
|
|
356
|
+
CREATE TABLE IF NOT EXISTS posts (
|
|
357
|
+
id TEXT PRIMARY KEY,
|
|
358
|
+
title TEXT NOT NULL,
|
|
359
|
+
content TEXT NOT NULL,
|
|
360
|
+
created_at TEXT NOT NULL
|
|
361
|
+
)
|
|
362
|
+
`);
|
|
363
|
+
|
|
364
|
+
Bun.serve({
|
|
365
|
+
routes: {
|
|
366
|
+
// List posts
|
|
367
|
+
"/api/posts": {
|
|
368
|
+
GET: () => {
|
|
369
|
+
const posts = db.query("SELECT * FROM posts").all();
|
|
370
|
+
return Response.json(posts);
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
// Create post
|
|
374
|
+
POST: async req => {
|
|
375
|
+
const post: Omit<Post, "id" | "created_at"> = await req.json();
|
|
376
|
+
const id = crypto.randomUUID();
|
|
377
|
+
|
|
378
|
+
db.query(
|
|
379
|
+
`INSERT INTO posts (id, title, content, created_at)
|
|
380
|
+
VALUES (?, ?, ?, ?)`,
|
|
381
|
+
).run(id, post.title, post.content, new Date().toISOString());
|
|
382
|
+
|
|
383
|
+
return Response.json({ id, ...post }, { status: 201 });
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
// Get post by ID
|
|
388
|
+
"/api/posts/:id": req => {
|
|
389
|
+
const post = db
|
|
390
|
+
.query("SELECT * FROM posts WHERE id = ?")
|
|
391
|
+
.get(req.params.id);
|
|
392
|
+
|
|
393
|
+
if (!post) {
|
|
394
|
+
return new Response("Not Found", { status: 404 });
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return Response.json(post);
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
|
|
401
|
+
error(error) {
|
|
402
|
+
console.error(error);
|
|
403
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
```ts#types.ts
|
|
409
|
+
export interface Post {
|
|
410
|
+
id: string;
|
|
411
|
+
title: string;
|
|
412
|
+
content: string;
|
|
413
|
+
created_at: string;
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
{% /codetabs %}
|
|
418
|
+
|
|
419
|
+
### Routing performance
|
|
420
|
+
|
|
421
|
+
`Bun.serve()`'s router builds on top uWebSocket's [tree-based approach](https://github.com/oven-sh/bun/blob/0d1a00fa0f7830f8ecd99c027fce8096c9d459b6/packages/bun-uws/src/HttpRouter.h#L57-L64) to add [SIMD-accelerated route parameter decoding](https://github.com/oven-sh/bun/blob/jarred/optional-fetch/src/bun.js/bindings/decodeURIComponentSIMD.cpp#L21-L271) and [JavaScriptCore structure caching](https://github.com/oven-sh/bun/blob/jarred/optional-fetch/src/bun.js/bindings/ServerRouteList.cpp#L100-L101) to push the performance limits of what modern hardware allows.
|
|
422
|
+
|
|
423
|
+
### `fetch` request handler
|
|
424
|
+
|
|
425
|
+
The `fetch` handler handles incoming requests that weren't matched by any route. It receives a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object and returns a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) or [`Promise<Response>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
Bun.serve({
|
|
429
|
+
fetch(req) {
|
|
430
|
+
const url = new URL(req.url);
|
|
431
|
+
if (url.pathname === "/") return new Response("Home page!");
|
|
432
|
+
if (url.pathname === "/blog") return new Response("Blog!");
|
|
433
|
+
return new Response("404!");
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
The `fetch` handler supports async/await:
|
|
439
|
+
|
|
440
|
+
```ts
|
|
441
|
+
import { sleep, serve } from "bun";
|
|
442
|
+
serve({
|
|
443
|
+
async fetch(req) {
|
|
444
|
+
const start = performance.now();
|
|
445
|
+
await sleep(10);
|
|
446
|
+
const end = performance.now();
|
|
447
|
+
return new Response(`Slept for ${end - start}ms`);
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Promise-based responses are also supported:
|
|
453
|
+
|
|
454
|
+
```ts
|
|
455
|
+
Bun.serve({
|
|
456
|
+
fetch(req) {
|
|
457
|
+
// Forward the request to another server.
|
|
458
|
+
return fetch("https://example.com");
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
You can also access the `Server` object from the `fetch` handler. It's the second argument passed to the `fetch` function.
|
|
464
|
+
|
|
465
|
+
```ts
|
|
466
|
+
// `server` is passed in as the second argument to `fetch`.
|
|
467
|
+
const server = Bun.serve({
|
|
468
|
+
fetch(req, server) {
|
|
469
|
+
const ip = server.requestIP(req);
|
|
470
|
+
return new Response(`Your IP is ${ip}`);
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
```
|
|
474
|
+
|
|
183
475
|
### Changing the `port` and `hostname`
|
|
184
476
|
|
|
185
477
|
To configure which port and hostname the server will listen on, set `port` and `hostname` in the options object.
|
|
@@ -553,7 +845,7 @@ Update the server's handlers without restarting:
|
|
|
553
845
|
|
|
554
846
|
```ts
|
|
555
847
|
const server = Bun.serve({
|
|
556
|
-
|
|
848
|
+
routes: {
|
|
557
849
|
"/api/version": Response.json({ version: "v1" }),
|
|
558
850
|
},
|
|
559
851
|
fetch(req) {
|
|
@@ -563,7 +855,7 @@ const server = Bun.serve({
|
|
|
563
855
|
|
|
564
856
|
// Update to new handler
|
|
565
857
|
server.reload({
|
|
566
|
-
|
|
858
|
+
routes: {
|
|
567
859
|
"/api/version": Response.json({ version: "v2" }),
|
|
568
860
|
},
|
|
569
861
|
fetch(req) {
|
|
@@ -572,7 +864,7 @@ server.reload({
|
|
|
572
864
|
});
|
|
573
865
|
```
|
|
574
866
|
|
|
575
|
-
This is useful for development and hot reloading. Only `fetch`, `error`, and `
|
|
867
|
+
This is useful for development and hot reloading. Only `fetch`, `error`, and `routes` can be updated.
|
|
576
868
|
|
|
577
869
|
## Per-Request Controls
|
|
578
870
|
|
package/docs/api/spawn.md
CHANGED
|
@@ -110,7 +110,7 @@ You can read results from the subprocess via the `stdout` and `stderr` propertie
|
|
|
110
110
|
```ts
|
|
111
111
|
const proc = Bun.spawn(["bun", "--version"]);
|
|
112
112
|
const text = await new Response(proc.stdout).text();
|
|
113
|
-
console.log(text); // => "1.2.3-canary.
|
|
113
|
+
console.log(text); // => "1.2.3-canary.20250218T140705"
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
Configure the output stream by passing one of the following values to `stdout/stderr`:
|
package/docs/api/sql.md
CHANGED
|
@@ -185,11 +185,19 @@ Note that simple queries cannot use parameters (`${value}`). If you need paramet
|
|
|
185
185
|
|
|
186
186
|
### Unsafe Queries
|
|
187
187
|
|
|
188
|
-
You can use the `sql.unsafe` function to execute raw SQL strings. Use this with caution, as it will not escape user input.
|
|
188
|
+
You can use the `sql.unsafe` function to execute raw SQL strings. Use this with caution, as it will not escape user input. Executing more than one command per query is allowed if no parameters are used.
|
|
189
189
|
|
|
190
190
|
```ts
|
|
191
|
+
// Multiple commands without parameters
|
|
192
|
+
const result = await sql.unsafe(`
|
|
193
|
+
SELECT ${userColumns} FROM users;
|
|
194
|
+
SELECT ${accountColumns} FROM accounts;
|
|
195
|
+
`);
|
|
196
|
+
|
|
197
|
+
// Using parameters (only one command is allowed)
|
|
191
198
|
const result = await sql.unsafe(
|
|
192
|
-
"SELECT " +
|
|
199
|
+
"SELECT " + dangerous + " FROM users WHERE id = $1",
|
|
200
|
+
[id],
|
|
193
201
|
);
|
|
194
202
|
```
|
|
195
203
|
|
|
@@ -451,28 +459,29 @@ try {
|
|
|
451
459
|
|
|
452
460
|
## Prepared Statements
|
|
453
461
|
|
|
454
|
-
By default, Bun's SQL client automatically creates prepared statements for queries where it can be inferred that the query is static. This provides better performance
|
|
462
|
+
By default, Bun's SQL client automatically creates named prepared statements for queries where it can be inferred that the query is static. This provides better performance. However, you can change this behavior by setting `prepare: false` in the connection options:
|
|
455
463
|
|
|
456
464
|
```ts
|
|
457
465
|
const sql = new SQL({
|
|
458
466
|
// ... other options ...
|
|
459
|
-
prepare: false, // Disable prepared statements
|
|
467
|
+
prepare: false, // Disable persisting named prepared statements on the server
|
|
460
468
|
});
|
|
461
469
|
```
|
|
462
470
|
|
|
463
|
-
When
|
|
471
|
+
When `prepare: false` is set:
|
|
472
|
+
|
|
473
|
+
Queries are still executed using the "extended" protocol, but they are executed using [unnamed prepared statements](https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY), an unnamed prepared statement lasts only until the next Parse statement specifying the unnamed statement as destination is issued.
|
|
464
474
|
|
|
465
|
-
-
|
|
466
|
-
- Each query is sent to the server as a raw SQL string
|
|
467
|
-
- Multiple statements can be executed in a single query (using `sql``.simple()`)
|
|
468
|
-
- Parameter binding is still safe against SQL injection, but simple queries cannot include parameters
|
|
475
|
+
- Parameter binding is still safe against SQL injection
|
|
469
476
|
- Each query is parsed and planned from scratch by the server
|
|
477
|
+
- Queries will not be [pipelined](https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-PIPELINING)
|
|
470
478
|
|
|
471
|
-
You might want to
|
|
479
|
+
You might want to use `prepare: false` when:
|
|
472
480
|
|
|
473
481
|
- Using PGBouncer in transaction mode (though since PGBouncer 1.21.0, protocol-level named prepared statements are supported when configured properly)
|
|
474
482
|
- Debugging query execution plans
|
|
475
483
|
- Working with dynamic SQL where query plans need to be regenerated frequently
|
|
484
|
+
- More than one command per query will not be supported (unless you use `sql``.simple()`)
|
|
476
485
|
|
|
477
486
|
Note that disabling prepared statements may impact performance for queries that are executed frequently with different parameters, as the server needs to parse and plan each query from scratch.
|
|
478
487
|
|
|
@@ -511,6 +520,7 @@ The client provides typed errors for different failure scenarios:
|
|
|
511
520
|
| `ERR_POSTGRES_SERVER_ERROR` | General error from PostgreSQL server |
|
|
512
521
|
| `ERR_POSTGRES_INVALID_QUERY_BINDING` | Invalid parameter binding |
|
|
513
522
|
| `ERR_POSTGRES_QUERY_CANCELLED` | Query was cancelled |
|
|
523
|
+
| `ERR_POSTGRES_NOT_TAGGED_CALL` | Query was called without a tagged call |
|
|
514
524
|
|
|
515
525
|
### Data Type Errors
|
|
516
526
|
|
|
@@ -3,26 +3,34 @@ Using `Bun.serve()`'s `routes` option, you can run your frontend and backend in
|
|
|
3
3
|
To get started, import HTML files and pass them to the `routes` option in `Bun.serve()`.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
+
import { sql, serve } from "bun";
|
|
6
7
|
import dashboard from "./dashboard.html";
|
|
7
8
|
import homepage from "./index.html";
|
|
8
9
|
|
|
9
|
-
const server =
|
|
10
|
-
// Add HTML imports to `routes` (before Bun v1.2.3, this was called `static`)
|
|
10
|
+
const server = serve({
|
|
11
11
|
routes: {
|
|
12
|
-
//
|
|
12
|
+
// ** HTML imports **
|
|
13
|
+
// Bundle & route index.html to "/". This uses HTMLRewriter to scan the HTML for `<script>` and `<link>` tags, run's Bun's JavaScript & CSS bundler on them, transpiles any TypeScript, JSX, and TSX, downlevels CSS with Bun's CSS parser and serves the result.
|
|
13
14
|
"/": homepage,
|
|
14
15
|
// Bundle & route dashboard.html to "/dashboard"
|
|
15
16
|
"/dashboard": dashboard,
|
|
16
17
|
|
|
17
|
-
// API endpoints
|
|
18
|
-
"/api/users":
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
// ** API endpoints ** (Bun v1.2.3+ required)
|
|
19
|
+
"/api/users": {
|
|
20
|
+
async GET(req) {
|
|
21
|
+
const users = await sql`SELECT * FROM users`;
|
|
22
|
+
return Response.json(users);
|
|
23
|
+
},
|
|
24
|
+
async POST(req) {
|
|
25
|
+
const { name, email } = await req.json();
|
|
26
|
+
const [user] =
|
|
27
|
+
await sql`INSERT INTO users (name, email) VALUES (${name}, ${email})`;
|
|
28
|
+
return Response.json(user);
|
|
29
|
+
},
|
|
21
30
|
},
|
|
22
|
-
|
|
23
31
|
"/api/users/:id": async req => {
|
|
24
32
|
const { id } = req.params;
|
|
25
|
-
const [user] = await
|
|
33
|
+
const [user] = await sql`SELECT * FROM users WHERE id = ${id}`;
|
|
26
34
|
return Response.json(user);
|
|
27
35
|
},
|
|
28
36
|
},
|
|
@@ -32,11 +40,11 @@ const server = Bun.serve({
|
|
|
32
40
|
// - Hot reloading (Bun v1.2.3+ required)
|
|
33
41
|
development: true,
|
|
34
42
|
|
|
35
|
-
//
|
|
36
|
-
async fetch(req) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
},
|
|
43
|
+
// Prior to v1.2.3, the `fetch` option was used to handle all API requests. It is now optional.
|
|
44
|
+
// async fetch(req) {
|
|
45
|
+
// // Return 404 for unmatched routes
|
|
46
|
+
// return new Response("Not Found", { status: 404 });
|
|
47
|
+
// },
|
|
40
48
|
});
|
|
41
49
|
|
|
42
50
|
console.log(`Listening on ${server.url}`);
|