@krisanalfa/bunest-adapter 0.0.1

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,866 @@
1
+ # Native Bun Adapter for NestJS
2
+
3
+ This project provides a native Bun adapter for NestJS, allowing developers to leverage the performance benefits of the Bun runtime while using the powerful features of the NestJS framework.
4
+
5
+ ## Features
6
+
7
+ ### Native Bun adapter for NestJS
8
+
9
+ Easy to set up and use Bun as the underlying HTTP server for your NestJS applications.
10
+
11
+ ```ts
12
+ import { BunAdapter } from "@krisanalfa/bunest-adapter";
13
+ import { Logger } from "@nestjs/common";
14
+ import { NestFactory } from "@nestjs/core";
15
+ import { Server } from "bun";
16
+
17
+ import { AppModule } from "./app.module.js";
18
+
19
+ async function main() {
20
+ const app = await NestFactory.create(AppModule, new BunAdapter());
21
+ await app.listen(3000);
22
+ const server = app.getHttpAdapter().getHttpServer() as Server<unknown>;
23
+ Logger.log(`Server started on ${server.url.toString()}`, "NestApplication");
24
+ }
25
+
26
+ await main();
27
+ ```
28
+
29
+ You can also listen on Unix sockets:
30
+
31
+ ```ts
32
+ await app.listen("/tmp/nestjs-bun.sock");
33
+
34
+ // Using Bun's fetch API to make requests over the Unix socket
35
+ fetch("http://localhost/", {
36
+ unix: "/tmp/nestjs-bun.sock",
37
+ });
38
+ ```
39
+
40
+ Or even an abstract namespace socket on Linux:
41
+
42
+ ```ts
43
+ await app.listen("\0nestjs-bun-abstract-socket");
44
+ ```
45
+
46
+ To configure the underlying Bun server options, you can pass them to the `BunAdapter` constructor:
47
+
48
+ ```ts
49
+ new BunAdapter({
50
+ id: "my-nestjs-bun-server",
51
+ development: true,
52
+ maxRequestBodySize: 10 * 1024 * 1024, // 10 MB
53
+ idleTimeout: 120_000, // 2 minutes
54
+ tls: {}, // TLS options here (read more about TLS options in the HTTPS section)
55
+ });
56
+ ```
57
+
58
+ Since Bun only supports these HTTP methods:
59
+
60
+ - GET
61
+ - POST
62
+ - PUT
63
+ - DELETE
64
+ - PATCH
65
+ - OPTIONS
66
+ - HEAD
67
+
68
+ ... the Bun adapter will throw an error if you try to use unsupported methods like ALL, COPY or SEARCH.
69
+
70
+ ```ts
71
+ @Controller("example")
72
+ class ExampleController {
73
+ @All() // This won't work with Bun adapter
74
+ handleAllMethods() {
75
+ //
76
+ }
77
+ }
78
+ ```
79
+
80
+ However, you can still define consumer middleware to use these methods as needed.
81
+
82
+ ```ts
83
+ @Controller("example")
84
+ class ExampleController {
85
+ @Get()
86
+ getExample() {
87
+ return { message: "This works!" };
88
+ }
89
+ }
90
+
91
+ class ExampleMiddleware implements NestMiddleware {
92
+ use(req: BunRequest, res: BunResponse, next: () => void) {
93
+ // Middleware logic here
94
+ // You may send response directly if needed
95
+ res.end("Middleware response");
96
+ next();
97
+ }
98
+ }
99
+
100
+ @Module({
101
+ controllers: [ExampleController],
102
+ providers: [ExampleMiddleware],
103
+ })
104
+ class ExampleModule implements NestModule {
105
+ configure(consumer: MiddlewareConsumer) {
106
+ consumer.apply(ExampleMiddleware).forRoutes({
107
+ path: "example",
108
+ method: RequestMethod.ALL,
109
+ });
110
+ }
111
+ }
112
+
113
+ const response = fetch("http://localhost:3000/example", {
114
+ method: "TRACE", // This will be handled by the middleware
115
+ });
116
+ console.log(await response.text()); // Outputs: "Middleware response"
117
+ ```
118
+
119
+ ### Full NestJS Feature Support
120
+
121
+ The Bun adapter supports all major NestJS features including:
122
+
123
+ #### Controllers & HTTP Methods
124
+
125
+ Full support for all HTTP methods and decorators:
126
+
127
+ ```ts
128
+ @Controller("users", { host: ":account.example.com" } /* Works too */)
129
+ class UsersController {
130
+ @Get()
131
+ findAll(@Query("search") search?: string) {
132
+ return { users: [] };
133
+ }
134
+
135
+ @Post()
136
+ create(@Body() data: CreateUserDto) {
137
+ return { created: data };
138
+ }
139
+
140
+ @Get(":id")
141
+ findOne(@Param("id", ParseIntPipe) id: number) {
142
+ return { user: { id } };
143
+ }
144
+
145
+ @Put(":id")
146
+ update(@Param("id") id: string, @Body() data: UpdateUserDto) {
147
+ return { updated: data };
148
+ }
149
+
150
+ @Delete(":id")
151
+ remove(@Param("id") id: string) {
152
+ return { deleted: id };
153
+ }
154
+
155
+ @Patch(":id")
156
+ partialUpdate(
157
+ @HostParam("account") account: string, // Works too
158
+ @Param("id") id: string,
159
+ @Body() data: Partial<UpdateUserDto>,
160
+ ) {
161
+ return { updated: data };
162
+ }
163
+ }
164
+ ```
165
+
166
+ ##### Notes on `@HostParam()` and `host` Controller option
167
+
168
+ The BunRequest's hostname always trusts the proxy headers (like `X-Forwarded-Host`) since Bun itself does not provide direct access to the raw socket information. When this header is not present, it falls back to the `Host` header. Ensure that your application is behind a trusted proxy when using `X-Forwarded-Host` to avoid host header injection attacks.
169
+
170
+ In express.js, you would typically configure trusted proxies using `app.set('trust proxy', true)`. See the docs [here](https://expressjs.com/en/guide/behind-proxies.html). But in Bun, this is not applicable. In the future, we may provide a way to configure trusted proxies directly in the Bun adapter. But for now, be cautious when using `@HostParam()` in untrusted environments.
171
+
172
+ #### Middleware
173
+
174
+ Full middleware support with route exclusion and global middleware:
175
+
176
+ ```ts
177
+ @Module({
178
+ controllers: [DummyController, AnotherDummyController],
179
+ providers: [DummyMiddleware],
180
+ })
181
+ class DummyModule implements NestModule {
182
+ configure(consumer: MiddlewareConsumer) {
183
+ consumer
184
+ .apply(DummyMiddleware)
185
+ .exclude({
186
+ path: "/dummy/skip",
187
+ method: RequestMethod.GET,
188
+ })
189
+ .forRoutes(DummyController);
190
+ consumer.apply(GlobalMiddleware).forRoutes("*");
191
+ }
192
+ }
193
+ ```
194
+
195
+ #### Guards
196
+
197
+ Protect routes with guards:
198
+
199
+ ```ts
200
+ @Injectable()
201
+ class AuthGuard implements CanActivate {
202
+ canActivate(context: ExecutionContext): boolean {
203
+ const request = context.switchToHttp().getRequest<BunRequest>();
204
+ return request.headers.get("authorization") !== null;
205
+ }
206
+ }
207
+
208
+ @Controller("protected")
209
+ class ProtectedController {
210
+ @Get()
211
+ @UseGuards(AuthGuard)
212
+ getData() {
213
+ return { data: "secret" };
214
+ }
215
+ }
216
+ ```
217
+
218
+ #### Interceptors
219
+
220
+ Including support for cancellable requests:
221
+
222
+ ```ts
223
+ @Injectable()
224
+ class CancellableInterceptor implements NestInterceptor {
225
+ intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
226
+ const request = context.switchToHttp().getRequest<BunRequest>();
227
+ const signal = request.signal;
228
+ const close$ = fromEvent(signal, "abort");
229
+
230
+ return next.handle().pipe(
231
+ takeUntil(
232
+ close$.pipe(
233
+ mergeMap(async () => {
234
+ // Cleanup on request cancellation
235
+ }),
236
+ ),
237
+ ),
238
+ defaultIfEmpty(null),
239
+ );
240
+ }
241
+ }
242
+ ```
243
+
244
+ #### Exception Filters
245
+
246
+ Custom exception handling:
247
+
248
+ ```ts
249
+ @Catch(HttpException)
250
+ class HttpExceptionFilter implements ExceptionFilter {
251
+ catch(exception: HttpException, host: ArgumentsHost) {
252
+ const ctx = host.switchToHttp();
253
+ const response = ctx.getResponse<BunResponse>();
254
+ const request = ctx.getRequest<BunRequest>();
255
+ const status = exception.getStatus();
256
+
257
+ response.setStatus(status);
258
+ response.end({
259
+ statusCode: status,
260
+ timestamp: new Date().toISOString(),
261
+ path: request.pathname,
262
+ message: exception.message,
263
+ });
264
+ }
265
+ }
266
+
267
+ // Usage
268
+ app.useGlobalFilters(new HttpExceptionFilter());
269
+ ```
270
+
271
+ #### Validation
272
+
273
+ Full support for class-validator and pipes:
274
+
275
+ ```ts
276
+ class CreateUserDto {
277
+ @IsString()
278
+ name!: string;
279
+
280
+ @IsNumber()
281
+ @Min(0)
282
+ age!: number;
283
+ }
284
+
285
+ @Controller("users")
286
+ class UsersController {
287
+ @Post()
288
+ create(@Body() dto: CreateUserDto) {
289
+ return { created: dto };
290
+ }
291
+ }
292
+
293
+ // Enable validation globally
294
+ app.useGlobalPipes(new ValidationPipe());
295
+ ```
296
+
297
+ #### File Uploads
298
+
299
+ Native file upload support using File API:
300
+
301
+ ```ts
302
+ @Controller("upload")
303
+ class UploadController {
304
+ @Post("single")
305
+ uploadFile(@UploadedFile("file") file: File) {
306
+ return { filename: file.name, size: file.size };
307
+ }
308
+
309
+ @Post("multiple")
310
+ uploadFiles(@UploadedFiles() files: File[]) {
311
+ return files.map((f) => ({ filename: f.name, size: f.size }));
312
+ }
313
+ }
314
+ ```
315
+
316
+ To work with Bun's native `BunFile` API for uploads, consider using the `BunFileInterceptor` provided by this package (see the Bun File API Support section below).
317
+
318
+ #### Streaming Responses
319
+
320
+ Support for [`StreamableFile`](https://docs.nestjs.com/techniques/streaming-files#streamable-file-class):
321
+
322
+ ```ts
323
+ @Controller("files")
324
+ class FilesController {
325
+ @Get("download")
326
+ @Header("Content-Type", "text/plain")
327
+ download() {
328
+ const buffer = new TextEncoder().encode("File content");
329
+ return new StreamableFile(buffer, {
330
+ type: "text/plain",
331
+ disposition: 'attachment; filename="file.txt"',
332
+ });
333
+ }
334
+ }
335
+ ```
336
+
337
+ #### Versioning
338
+
339
+ Full API [versioning](https://docs.nestjs.com/techniques/versioning) support (URI, Header, Media Type, Custom):
340
+
341
+ ```ts
342
+ // URI Versioning
343
+ app.enableVersioning({
344
+ type: VersioningType.URI,
345
+ });
346
+
347
+ @Controller("cats")
348
+ @Version("1")
349
+ class CatsControllerV1 {
350
+ @Get()
351
+ findAll() {
352
+ return { version: "1", cats: [] };
353
+ }
354
+ }
355
+
356
+ @Controller("cats")
357
+ @Version("2")
358
+ class CatsControllerV2 {
359
+ @Get()
360
+ findAll() {
361
+ return { version: "2", cats: [], pagination: true };
362
+ }
363
+ }
364
+
365
+ // Access via: /v1/cats and /v2/cats
366
+ ```
367
+
368
+ #### CORS
369
+
370
+ Built-in CORS support with dynamic configuration:
371
+
372
+ ```ts
373
+ // Simple CORS
374
+ app.enableCors();
375
+
376
+ // Advanced CORS with dynamic options
377
+ app.enableCors((req: BunRequest, callback) => {
378
+ const origin = req.headers.get("origin");
379
+
380
+ if (origin === "https://allowed.example.com") {
381
+ callback(null, {
382
+ origin: true,
383
+ credentials: true,
384
+ methods: ["GET", "POST"],
385
+ });
386
+ } else {
387
+ callback(null, { origin: false });
388
+ }
389
+ });
390
+ ```
391
+
392
+ You can also use NestJS's `CorsOptions` type for static configuration.
393
+
394
+ ```ts
395
+ const app = await NestFactory.create(AppModule, new BunAdapter(), {
396
+ cors: {
397
+ origin: "https://example.com",
398
+ methods: ["GET", "POST", "PUT"],
399
+ credentials: true,
400
+ },
401
+ });
402
+ ```
403
+
404
+ #### Cookies
405
+
406
+ Full cookie support:
407
+
408
+ ```ts
409
+ @Controller()
410
+ class CookiesController {
411
+ @Get("set")
412
+ setCookie(@Res({ passthrough: true }) response: BunResponse) {
413
+ response.cookie("session", "abc123", {
414
+ httpOnly: true,
415
+ maxAge: 3600000,
416
+ });
417
+ return { message: "Cookie set" };
418
+ }
419
+
420
+ @Get("get")
421
+ getCookie(@Req() request: BunRequest) {
422
+ const session = request.cookies.get("session");
423
+ return { session };
424
+ }
425
+ }
426
+ ```
427
+
428
+ #### Popular Express Middleware
429
+
430
+ Compatible with popular Express middleware:
431
+
432
+ ```ts
433
+ import helmet from "helmet";
434
+
435
+ const app = await NestFactory.create(AppModule, new BunAdapter());
436
+ app.use(helmet());
437
+ ```
438
+
439
+ Tested and working with:
440
+
441
+ - `helmet` - Security headers
442
+ - `cors` - CORS handling
443
+ - And most other Express-compatible middleware
444
+
445
+ ### Bun File API Support
446
+
447
+ This package provides first-class support for Bun's native [`BunFile`](https://bun.com/docs/runtime/file-io) API, enabling seamless file uploads and downloads using Bun's efficient file handling capabilities.
448
+
449
+ #### BunFileInterceptor
450
+
451
+ The `BunFileInterceptor` is a NestJS interceptor that processes file uploads using Bun's native `BunFile` API. It automatically saves uploaded files to a temporary directory and attaches them to the request object, making them available in your controller methods via the `@UploadedFile()` or `@UploadedFiles()` decorators.
452
+
453
+ **How it works:**
454
+
455
+ - When a request with file uploads is received, the interceptor:
456
+
457
+ - Saves each uploaded file to a directory set in `BUN_UPLOAD_DIR` environment variables. If not set, it falls back to a unique temporary directory (using Bun's `Bun.write` and `randomUUIDv7()` for isolation). Here's how we determine the upload path if `BUN_UPLOAD_DIR` is not set:
458
+
459
+ ```ts
460
+ import { tempdir } from "os";
461
+ import { join } from "path";
462
+ import { randomUUIDv7 } from "bun";
463
+ const uploadPath = join(tempdir(), "uploads", SERVER_ID, randomUUIDv7());
464
+ ```
465
+
466
+ `SERVER_ID` is a unique identifier for the Bun server instance, ensuring that uploads from different server instances do not conflict. See the [Bun documentation](https://bun.com/docs/runtime/http/server#reference) for more details on server id.
467
+
468
+ - Replaces the uploaded file(s) in the request with Bun's `BunFile` objects pointing to the saved files.
469
+
470
+ #### Usage Example
471
+
472
+ ```ts
473
+ @Controller()
474
+ class UploadController {
475
+ @Post("single")
476
+ @UseInterceptors(BunFileInterceptor)
477
+ uploadSingle(
478
+ @Res({ passthrough: true }) res: BunResponse,
479
+ @UploadedFile() file?: BunFile,
480
+ ) {
481
+ res.end(file); // Optimized to send BunFile directly
482
+ }
483
+
484
+ @Post("multiple")
485
+ @UseInterceptors(BunFileInterceptor)
486
+ uploadMultiple(
487
+ @UploadedFiles() files: BunFile[],
488
+ @Res({ passthrough: true }) res: BunResponse,
489
+ ) {
490
+ // Return the last file for demonstration
491
+ res.end(files[files.length - 1]);
492
+ }
493
+ }
494
+ ```
495
+
496
+ **Best Practices:**
497
+ You should never use the `BunFileInterceptor` globally, as it incurs overhead for all requests. Instead, apply it only to specific routes that handle file uploads.
498
+
499
+ ### HTTPS
500
+
501
+ You can run your NestJS application with HTTPS using two approaches:
502
+
503
+ #### Using Bun's built-in HTTPS support (recommended)
504
+
505
+ ```ts
506
+ const app = await NestFactory.create(
507
+ AppModule,
508
+ new BunAdapter({
509
+ tls: {
510
+ cert: Bun.file("/path/to/cert.pem"),
511
+ key: Bun.file("/path/to/key.pem"),
512
+ },
513
+ }),
514
+ );
515
+ ```
516
+
517
+ #### Using NestJS App Factory HTTPS options
518
+
519
+ ```ts
520
+ const app = await NestFactory.create(
521
+ AppModule,
522
+ new BunAdapter(/* leave it empty */),
523
+ {
524
+ httpsOptions: {
525
+ cert: fs.readFileSync("/path/to/cert.pem"), // works with Bun too
526
+ key: Bun.file("/path/to/key.pem"), // you can use Bun.file here as well
527
+ },
528
+ },
529
+ );
530
+ ```
531
+
532
+ ##### Limitations
533
+
534
+ If you're using NestJS's built-in HTTPS options, only these options will be passed to Bun:
535
+
536
+ - `key`
537
+ - `cert`
538
+ - `ca`
539
+ - `passphrase`
540
+ - `ciphers`
541
+ - `secureOptions`
542
+ - `requestCert`
543
+
544
+ Other options will be ignored.
545
+
546
+ ### Code Quality
547
+
548
+ The Bun adapter is developed with high code quality standards, including:
549
+
550
+ - Strict TypeScript typings
551
+ - Strict linting rules (ESLint)
552
+ - Comprehensive unit and integration tests
553
+ - Coverage reports (>98% line coverage, >85% function coverage)
554
+
555
+ ## Request / Response Objects
556
+
557
+ The Bun adapter provides `BunRequest` and `BunResponse` classes that wrap Bun's native `Request` and `Response` objects, adding NestJS-specific functionality with performance optimizations through lazy parsing and caching.
558
+
559
+ ### BunRequest
560
+
561
+ A high-performance request wrapper that provides lazy parsing and caching for optimal performance. Properties like `pathname`, `hostname`, `query`, and `headers` are only parsed when first accessed.
562
+
563
+ #### Properties
564
+
565
+ - **`url`** - The complete request URL
566
+ - **`method`** - HTTP method (GET, POST, etc.)
567
+ - **`pathname`** - URL path (lazily parsed)
568
+ - **`hostname`** - Host name without port (lazily parsed)
569
+ - **`query`** - Parsed query parameters (lazily parsed)
570
+ - **`headers`** - Request headers object with `.get()` method (lazily materialized)
571
+ - **`params`** - Route parameters from URL patterns
572
+ - **`body`** - Parsed request body (set by body parser middleware)
573
+ - **`rawBody`** - Raw request body as ArrayBuffer
574
+ - **`file`** - Single uploaded file (for single file uploads)
575
+ - **`files`** - Multiple uploaded files (for multi-file uploads)
576
+ - **`signal`** - AbortSignal for request cancellation
577
+ - **`cookies`** - Cookie map for accessing request cookies
578
+
579
+ #### Methods
580
+
581
+ - **`get(key)`** - Get custom property stored in request
582
+ - **`set(key, value)`** - Set custom property in request (useful for middleware)
583
+ - **`json()`** - Parse body as JSON
584
+ - **`text()`** - Read body as text
585
+ - **`formData()`** - Parse body as FormData
586
+ - **`arrayBuffer()`** - Read body as ArrayBuffer
587
+ - **`blob()`** - Read body as Blob
588
+ - **`bytes()`** - Read body as Uint8Array
589
+ - **`clone()`** - Create a deep clone of the request
590
+
591
+ #### Usage Examples
592
+
593
+ ```ts
594
+ @Controller("users")
595
+ class UsersController {
596
+ @Get()
597
+ findAll(@Req() req: BunRequest) {
598
+ // Access query parameters
599
+ const page = req.query.page; // Lazily parsed
600
+ const limit = req.query.limit;
601
+
602
+ // Access headers
603
+ const auth = req.headers.get("authorization");
604
+ const contentType = req.headers["content-type"];
605
+
606
+ return { page, limit, auth };
607
+ }
608
+
609
+ @Post()
610
+ create(@Req() req: BunRequest, @Body() dto: CreateUserDto) {
611
+ // Access parsed body
612
+ console.log(req.body); // Same as dto
613
+
614
+ // Access request info
615
+ console.log(req.pathname); // "/users"
616
+ console.log(req.hostname); // "localhost"
617
+ console.log(req.method); // "POST"
618
+
619
+ return { created: dto };
620
+ }
621
+
622
+ @Post("upload")
623
+ upload(@Req() req: BunRequest, @UploadedFile() file: File) {
624
+ // Access uploaded file
625
+ console.log(req.file?.name);
626
+ console.log(req.file?.size);
627
+
628
+ return { filename: file.name, size: file.size };
629
+ }
630
+ }
631
+ ```
632
+
633
+ **Storing custom data between middleware and handlers:**
634
+
635
+ ```ts
636
+ @Injectable()
637
+ class AuthMiddleware implements NestMiddleware {
638
+ use(req: BunRequest, res: BunResponse, next: () => void) {
639
+ // Store user data in request
640
+ req.set("user", { id: 1, name: "John" });
641
+ req.set("requestTime", Date.now());
642
+ next();
643
+ }
644
+ }
645
+
646
+ @Controller("api")
647
+ class ApiController {
648
+ @Get("profile")
649
+ getProfile(@Req() req: BunRequest) {
650
+ // Retrieve stored data
651
+ const user = req.get("user");
652
+ const requestTime = req.get("requestTime");
653
+
654
+ return { user, requestTime };
655
+ }
656
+ }
657
+ ```
658
+
659
+ ### BunResponse
660
+
661
+ A high-performance response builder that provides methods to construct responses with headers, cookies, and various body types. Uses lazy initialization and optimized response building for maximum performance.
662
+
663
+ #### Methods
664
+
665
+ - **`setStatus(code)`** - Set HTTP status code
666
+ - **`getStatus()`** - Get current status code
667
+ - **`setHeader(name, value)`** - Set a response header
668
+ - **`getHeader(name)`** - Get a response header value
669
+ - **`appendHeader(name, value)`** - Append value to existing header (comma-separated per RFC 9110)
670
+ - **`removeHeader(name)`** - Remove a response header
671
+ - **`cookie(name, value)`** - Set a cookie with name and value
672
+ - **`cookie(options)`** - Set a cookie with detailed options
673
+ - **`deleteCookie(name)`** - Delete a cookie by name
674
+ - **`deleteCookie(options)`** - Delete a cookie with path/domain options
675
+ - **`redirect(url, statusCode?)`** - Send redirect response (default 302)
676
+ - **`end(body?)`** - End response and send body (auto-handles JSON, streams, binary, or even [`BunFile`](https://bun.com/docs/runtime/file-io))
677
+ - **`res()`** - Get the native Response promise
678
+ - **`isEnded()`** - Check if response has been ended
679
+
680
+ #### Usage Examples
681
+
682
+ **Setting status and headers:**
683
+
684
+ ```ts
685
+ @Controller("api")
686
+ class ApiController {
687
+ @Get("custom")
688
+ custom(@Res() res: BunResponse) {
689
+ res.setStatus(201);
690
+ res.setHeader("X-Custom-Header", "custom-value");
691
+ res.setHeader("Cache-Control", "no-cache");
692
+ res.end({ message: "Success" });
693
+ }
694
+
695
+ @Get("append")
696
+ appendHeaders(@Res() res: BunResponse) {
697
+ res.setHeader("Cache-Control", "no-cache");
698
+ res.appendHeader("Cache-Control", "no-store");
699
+ // Results in: "Cache-Control: no-cache, no-store"
700
+ res.end({ message: "Done" });
701
+ }
702
+ }
703
+ ```
704
+
705
+ **Working with cookies:**
706
+
707
+ ```ts
708
+ @Controller("auth")
709
+ class AuthController {
710
+ @Post("login")
711
+ login(@Res() res: BunResponse) {
712
+ // Simple cookie
713
+ res.cookie("session", "abc123");
714
+
715
+ // Cookie with options
716
+ res.cookie({
717
+ name: "auth",
718
+ value: "token123",
719
+ httpOnly: true,
720
+ secure: true,
721
+ maxAge: 3600000, // 1 hour
722
+ path: "/",
723
+ sameSite: "strict",
724
+ });
725
+
726
+ res.end({ message: "Logged in" });
727
+ }
728
+
729
+ @Post("logout")
730
+ logout(@Res() res: BunResponse) {
731
+ res.deleteCookie("session");
732
+ res.deleteCookie("auth", { path: "/", domain: "example.com" });
733
+ res.end({ message: "Logged out" });
734
+ }
735
+ }
736
+ ```
737
+
738
+ **Redirects:**
739
+
740
+ ```ts
741
+ @Controller("redirect")
742
+ class RedirectController {
743
+ @Get("temporary")
744
+ temporaryRedirect(@Res() res: BunResponse) {
745
+ res.redirect("/new-location"); // 302
746
+ }
747
+
748
+ @Get("permanent")
749
+ @Redirect("https://example.com", 301) // Works too
750
+ permanentRedirect(@Res() res: BunResponse) {
751
+ //
752
+ }
753
+ }
754
+ ```
755
+
756
+ **Different response types:**
757
+
758
+ ```ts
759
+ @Controller("files")
760
+ class FilesController {
761
+ @Get("json")
762
+ sendJson(@Res() res: BunResponse) {
763
+ res.end({ data: [1, 2, 3] }); // Auto-serialized as JSON
764
+ }
765
+
766
+ @Get("empty")
767
+ sendEmpty(@Res() res: BunResponse) {
768
+ res.setStatus(204);
769
+ res.end(); // No body
770
+ }
771
+
772
+ @Get("binary")
773
+ sendBinary(@Res() res: BunResponse) {
774
+ const buffer = new Uint8Array([1, 2, 3, 4, 5]);
775
+ res.setHeader("Content-Type", "application/octet-stream");
776
+ res.end(buffer);
777
+ }
778
+
779
+ @Get("stream")
780
+ sendStream(@Res() res: BunResponse) {
781
+ const content = new TextEncoder().encode("File content");
782
+ const file = new StreamableFile(content, {
783
+ type: "text/plain",
784
+ disposition: 'attachment; filename="file.txt"',
785
+ });
786
+ res.end(file);
787
+ }
788
+ }
789
+ ```
790
+
791
+ **Using with passthrough mode:**
792
+
793
+ ```ts
794
+ @Controller("hybrid")
795
+ class HybridController {
796
+ @Get("passthrough")
797
+ withPassthrough(@Res({ passthrough: true }) res: BunResponse) {
798
+ // Set headers/cookies but let NestJS handle the response
799
+ res.setStatus(201);
800
+ res.setHeader("X-Custom", "value");
801
+ res.cookie("session", "abc123");
802
+
803
+ // Return value will be serialized by NestJS
804
+ return { message: "Created" };
805
+ }
806
+
807
+ @Get("manual")
808
+ manualResponse(@Res() res: BunResponse) {
809
+ // Full control - must call end()
810
+ res.setStatus(200);
811
+ res.end({ message: "Manual response" });
812
+ }
813
+ }
814
+ ```
815
+
816
+ ## Benchmark Results
817
+
818
+ Tested on MacOS Sequoia (15.6.1), Apple M1 Max (64GB RAM), Bun 1.3.5, Node.js 20.10.0
819
+
820
+ | Configuration | Requests/sec | Compared to Pure Bun |
821
+ | --------------------------------------------------------------------------------- | -----------: | -------------------: |
822
+ | Pure Bun | 80,742.72 | 100.00% |
823
+ | Nest + Bun + Native Bun Adapter | 69,665.59 | 86.32% |
824
+ | Nest + Bun + Express Adapter | 43,375.97 | 53.72% |
825
+ | Nest + Bun + [Hono Adapter](https://www.npmjs.com/package/@kiyasov/platform-hono) | 19,194.78 | 23.77% |
826
+ | Nest + Node + Express | 14,019.88 | 17.36% |
827
+
828
+ > **Pure Bun** is the fastest at **80,743 req/s**. **Nest + Bun + Native Bun Adapter** achieves **~86%** of Pure Bun's performance while providing full NestJS features, and is **~5x faster** than Nest + Node + Express. Compared to Bun with Express adapter, the native Bun adapter is **~1.6x faster**.
829
+
830
+ ### Running Benchmarks
831
+
832
+ This project includes benchmark configurations in the `benchmarks` directory.
833
+ To run the specific server benchmark, you can use predefined scripts in `package.json`. For example:
834
+
835
+ ```bash
836
+ # Running native bun server benchmark
837
+ bun run native
838
+
839
+ # Running NestJS with Bun adapter benchmark
840
+ bun run bun
841
+
842
+ # Running NestJS with Hono adapter benchmark
843
+ bun run hono
844
+
845
+ # Running NestJS with Express adapter benchmark
846
+ bun run express
847
+
848
+ # Running NestJS with Node and Express benchmark
849
+ bun run node
850
+ ```
851
+
852
+ All benchmarks use port `3000` by default. You can adjust the port in the respective benchmark files if needed.
853
+
854
+ ## Contributing
855
+
856
+ Contributions are welcome! Please open issues or submit pull requests for bug fixes, improvements, or new features.
857
+
858
+ ## Future Plans
859
+
860
+ - Support for WebSocket integration with Bun
861
+ - Enhanced trusted proxy configuration for host header handling
862
+ - Additional performance optimizations and benchmarks
863
+
864
+ ## License
865
+
866
+ MIT License. See the [LICENSE](./LICENSE) file for details.