@dangao/bun-server 1.1.2 → 1.1.4
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/docs/api.md +602 -0
- package/docs/best-practices.md +12 -0
- package/docs/custom-decorators.md +440 -0
- package/docs/deployment.md +447 -0
- package/docs/error-handling.md +462 -0
- package/docs/extensions.md +569 -0
- package/docs/guide.md +634 -0
- package/docs/migration.md +10 -0
- package/docs/performance.md +452 -0
- package/docs/troubleshooting.md +286 -0
- package/docs/zh/api.md +168 -0
- package/docs/zh/best-practices.md +38 -0
- package/docs/zh/custom-decorators.md +466 -0
- package/docs/zh/deployment.md +445 -0
- package/docs/zh/error-handling.md +456 -0
- package/docs/zh/extensions.md +584 -0
- package/docs/zh/guide.md +361 -0
- package/docs/zh/migration.md +86 -0
- package/docs/zh/performance.md +451 -0
- package/docs/zh/troubleshooting.md +279 -0
- package/package.json +4 -3
package/docs/guide.md
ADDED
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
# Usage Guide
|
|
2
|
+
|
|
3
|
+
Covers key steps for building Bun Server applications from scratch.
|
|
4
|
+
|
|
5
|
+
## 1. Initialize Application
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import "reflect-metadata";
|
|
9
|
+
import { Application } from "@dangao/bun-server";
|
|
10
|
+
|
|
11
|
+
const app = new Application({ port: 3000 });
|
|
12
|
+
app.listen();
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
> Tip: Default port is 3000, can be adjusted via `app.listen(customPort)` or
|
|
16
|
+
> `new Application({ port })`.
|
|
17
|
+
|
|
18
|
+
## 2. Register Controllers and Dependencies
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import {
|
|
22
|
+
Application,
|
|
23
|
+
Body,
|
|
24
|
+
Controller,
|
|
25
|
+
GET,
|
|
26
|
+
Injectable,
|
|
27
|
+
Param,
|
|
28
|
+
POST,
|
|
29
|
+
} from "@dangao/bun-server";
|
|
30
|
+
|
|
31
|
+
@Injectable()
|
|
32
|
+
class UserService {
|
|
33
|
+
public findById(id: string) {
|
|
34
|
+
return { id, name: "Alice" };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@Controller("/api/users")
|
|
39
|
+
class UserController {
|
|
40
|
+
public constructor(private readonly userService: UserService) {}
|
|
41
|
+
|
|
42
|
+
@GET("/:id")
|
|
43
|
+
public getUser(@Param("id") id: string) {
|
|
44
|
+
return this.userService.findById(id);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const app = new Application({ port: 3000 });
|
|
49
|
+
app.getContainer().register(UserService);
|
|
50
|
+
app.registerController(UserController);
|
|
51
|
+
app.listen();
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 3. Using Middleware
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import {
|
|
58
|
+
createCorsMiddleware,
|
|
59
|
+
createLoggerMiddleware,
|
|
60
|
+
} from "@dangao/bun-server";
|
|
61
|
+
|
|
62
|
+
const app = new Application({ port: 3000 });
|
|
63
|
+
|
|
64
|
+
// Global middleware
|
|
65
|
+
app.use(createLoggerMiddleware({ prefix: "[App]" }));
|
|
66
|
+
app.use(createCorsMiddleware({ origin: "*" }));
|
|
67
|
+
|
|
68
|
+
// Custom middleware
|
|
69
|
+
app.use(async (ctx, next) => {
|
|
70
|
+
console.log("Before request");
|
|
71
|
+
const response = await next();
|
|
72
|
+
console.log("After request");
|
|
73
|
+
return response;
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 4. Parameter Validation
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { IsEmail, IsString, MinLength, Validate } from "@dangao/bun-server";
|
|
81
|
+
|
|
82
|
+
@Controller("/api/users")
|
|
83
|
+
class UserController {
|
|
84
|
+
@POST("/")
|
|
85
|
+
public createUser(
|
|
86
|
+
@Body("name") @Validate(IsString(), MinLength(3)) name: string,
|
|
87
|
+
@Body("email") @Validate(IsEmail()) email: string,
|
|
88
|
+
) {
|
|
89
|
+
return { name, email };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 5. WebSocket Gateway
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { OnMessage, WebSocketGateway } from "@dangao/bun-server";
|
|
98
|
+
|
|
99
|
+
@WebSocketGateway("/ws")
|
|
100
|
+
class ChatGateway {
|
|
101
|
+
@OnMessage
|
|
102
|
+
public handleMessage(ws: ServerWebSocket, message: string) {
|
|
103
|
+
ws.send(`Echo: ${message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const app = new Application({ port: 3000 });
|
|
108
|
+
app.registerWebSocketGateway(ChatGateway);
|
|
109
|
+
app.listen();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 6. File Upload and Static Resources
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import {
|
|
116
|
+
createFileUploadMiddleware,
|
|
117
|
+
createStaticFileMiddleware,
|
|
118
|
+
} from "@dangao/bun-server";
|
|
119
|
+
|
|
120
|
+
const app = new Application({ port: 3000 });
|
|
121
|
+
|
|
122
|
+
// File upload
|
|
123
|
+
app.use(createFileUploadMiddleware({ maxSize: 5 * 1024 * 1024 }));
|
|
124
|
+
|
|
125
|
+
// Static files
|
|
126
|
+
app.use(createStaticFileMiddleware({ root: "./public", prefix: "/assets" }));
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## 7. Error Handling and Custom Filters
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { ExceptionFilterRegistry, HttpException } from "@dangao/bun-server";
|
|
133
|
+
|
|
134
|
+
@Controller("/api")
|
|
135
|
+
class ApiController {
|
|
136
|
+
@GET("/error")
|
|
137
|
+
public throwError() {
|
|
138
|
+
throw new HttpException(400, "Bad Request");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Register custom exception filter
|
|
143
|
+
ExceptionFilterRegistry.register(HttpException, (error, ctx) => {
|
|
144
|
+
return ctx.createResponse({ error: error.message }, { status: error.status });
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## 8. Extension System
|
|
149
|
+
|
|
150
|
+
Bun Server provides multiple extension methods, including middleware,
|
|
151
|
+
application extensions, module system, etc. For detailed information, please
|
|
152
|
+
refer to [Extension System Documentation](./extensions.md).
|
|
153
|
+
|
|
154
|
+
### Quick Examples
|
|
155
|
+
|
|
156
|
+
#### Using Module Approach (Recommended)
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import {
|
|
160
|
+
LoggerModule,
|
|
161
|
+
LogLevel,
|
|
162
|
+
Module,
|
|
163
|
+
SwaggerModule,
|
|
164
|
+
} from "@dangao/bun-server";
|
|
165
|
+
|
|
166
|
+
// Configure modules
|
|
167
|
+
LoggerModule.forRoot({
|
|
168
|
+
logger: { prefix: "App", level: LogLevel.INFO },
|
|
169
|
+
enableRequestLogging: true,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
SwaggerModule.forRoot({
|
|
173
|
+
info: { title: "API", version: "1.0.0" },
|
|
174
|
+
uiPath: "/swagger",
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
@Module({
|
|
178
|
+
imports: [LoggerModule, SwaggerModule],
|
|
179
|
+
controllers: [UserController],
|
|
180
|
+
providers: [UserService],
|
|
181
|
+
})
|
|
182
|
+
class AppModule {}
|
|
183
|
+
|
|
184
|
+
const app = new Application({ port: 3000 });
|
|
185
|
+
app.registerModule(AppModule);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### Using Extension Approach
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { LoggerExtension, SwaggerExtension } from "@dangao/bun-server";
|
|
192
|
+
|
|
193
|
+
const app = new Application({ port: 3000 });
|
|
194
|
+
|
|
195
|
+
app.registerExtension(new LoggerExtension({ prefix: "App" }));
|
|
196
|
+
app.registerExtension(
|
|
197
|
+
new SwaggerExtension({
|
|
198
|
+
info: { title: "API", version: "1.0.0" },
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### Using Middleware
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import {
|
|
207
|
+
createCorsMiddleware,
|
|
208
|
+
createLoggerMiddleware,
|
|
209
|
+
} from "@dangao/bun-server";
|
|
210
|
+
|
|
211
|
+
const app = new Application({ port: 3000 });
|
|
212
|
+
|
|
213
|
+
app.use(createLoggerMiddleware({ prefix: "[App]" }));
|
|
214
|
+
app.use(createCorsMiddleware({ origin: "*" }));
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
For more extension methods and use cases, please refer to
|
|
218
|
+
[Extension System Documentation](./extensions.md).
|
|
219
|
+
|
|
220
|
+
### Advanced Example: Interface + Symbol + Module
|
|
221
|
+
|
|
222
|
+
This example demonstrates using interfaces with Symbol tokens and module-based
|
|
223
|
+
dependency injection for more flexible decoupled design:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import {
|
|
227
|
+
Application,
|
|
228
|
+
Body,
|
|
229
|
+
CONFIG_SERVICE_TOKEN,
|
|
230
|
+
ConfigModule,
|
|
231
|
+
ConfigService,
|
|
232
|
+
Controller,
|
|
233
|
+
GET,
|
|
234
|
+
Inject,
|
|
235
|
+
Injectable,
|
|
236
|
+
Module,
|
|
237
|
+
Param,
|
|
238
|
+
POST,
|
|
239
|
+
} from "@dangao/bun-server";
|
|
240
|
+
|
|
241
|
+
// Define service interface
|
|
242
|
+
interface UserService {
|
|
243
|
+
find(id: string): Promise<{ id: string; name: string } | undefined>;
|
|
244
|
+
create(name: string): { id: string; name: string };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Create Symbol token for DI
|
|
248
|
+
const UserService = Symbol("UserService");
|
|
249
|
+
|
|
250
|
+
// Implement the interface
|
|
251
|
+
@Injectable()
|
|
252
|
+
class UserServiceImpl implements UserService {
|
|
253
|
+
private readonly users = new Map<string, { id: string; name: string }>([
|
|
254
|
+
["1", { id: "1", name: "Alice" }],
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
public async find(id: string) {
|
|
258
|
+
return this.users.get(id);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public create(name: string) {
|
|
262
|
+
const id = String(this.users.size + 1);
|
|
263
|
+
const user = { id, name };
|
|
264
|
+
this.users.set(id, user);
|
|
265
|
+
return user;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
@Controller("/api/users")
|
|
270
|
+
class UserController {
|
|
271
|
+
public constructor(
|
|
272
|
+
private readonly service: UserService,
|
|
273
|
+
@Inject(CONFIG_SERVICE_TOKEN) private readonly config: ConfigService,
|
|
274
|
+
) {}
|
|
275
|
+
|
|
276
|
+
@GET("/:id")
|
|
277
|
+
public async getUser(@Param("id") id: string) {
|
|
278
|
+
const user = await this.service.find(id);
|
|
279
|
+
if (!user) {
|
|
280
|
+
return { error: "Not Found" };
|
|
281
|
+
}
|
|
282
|
+
return user;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
@POST("/")
|
|
286
|
+
public createUser(@Body("name") name: string) {
|
|
287
|
+
return this.service.create(name);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Define module with Symbol-based provider
|
|
292
|
+
@Module({
|
|
293
|
+
controllers: [UserController],
|
|
294
|
+
providers: [
|
|
295
|
+
{
|
|
296
|
+
provide: UserService,
|
|
297
|
+
useClass: UserServiceImpl,
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
exports: [UserService],
|
|
301
|
+
})
|
|
302
|
+
class UserModule {}
|
|
303
|
+
|
|
304
|
+
// Configure modules
|
|
305
|
+
ConfigModule.forRoot({
|
|
306
|
+
defaultConfig: {
|
|
307
|
+
app: {
|
|
308
|
+
name: "Advanced App",
|
|
309
|
+
port: 3100,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Register module and start application
|
|
315
|
+
@Module({
|
|
316
|
+
imports: [ConfigModule],
|
|
317
|
+
controllers: [UserController],
|
|
318
|
+
providers: [
|
|
319
|
+
{
|
|
320
|
+
provide: UserService,
|
|
321
|
+
useClass: UserServiceImpl,
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
})
|
|
325
|
+
class AppModule {}
|
|
326
|
+
|
|
327
|
+
const app = new Application({ port: 3100 });
|
|
328
|
+
app.registerModule(AppModule);
|
|
329
|
+
app.listen();
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Key points:**
|
|
333
|
+
|
|
334
|
+
- **Interface-based design**: Define contracts with TypeScript interfaces for
|
|
335
|
+
better decoupling and testing
|
|
336
|
+
- **Symbol tokens**: Use `Symbol()` for type-safe dependency injection tokens,
|
|
337
|
+
avoiding naming conflicts with string tokens
|
|
338
|
+
- **Module providers**: Register providers using
|
|
339
|
+
`provide: Symbol, useClass: Implementation` to separate interface from
|
|
340
|
+
implementation
|
|
341
|
+
- **Type-safe injection**: Use interface types directly in constructors, the
|
|
342
|
+
framework will automatically resolve the implementation via Symbol token
|
|
343
|
+
|
|
344
|
+
This pattern is especially suitable for large projects, allowing easy
|
|
345
|
+
implementation replacement without affecting consumer code.
|
|
346
|
+
|
|
347
|
+
## 9. Database Integration
|
|
348
|
+
|
|
349
|
+
### Basic Database Connection
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
import {
|
|
353
|
+
DATABASE_SERVICE_TOKEN,
|
|
354
|
+
DatabaseModule,
|
|
355
|
+
DatabaseService,
|
|
356
|
+
Inject,
|
|
357
|
+
} from "@dangao/bun-server";
|
|
358
|
+
|
|
359
|
+
// Configure database
|
|
360
|
+
DatabaseModule.forRoot({
|
|
361
|
+
database: {
|
|
362
|
+
type: "postgres", // or 'mysql', 'sqlite'
|
|
363
|
+
config: {
|
|
364
|
+
host: "localhost",
|
|
365
|
+
port: 5432,
|
|
366
|
+
database: "mydb",
|
|
367
|
+
user: "user",
|
|
368
|
+
password: "password",
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
@Injectable()
|
|
374
|
+
class UserService {
|
|
375
|
+
public constructor(
|
|
376
|
+
@Inject(DATABASE_SERVICE_TOKEN) private readonly db: DatabaseService,
|
|
377
|
+
) {}
|
|
378
|
+
|
|
379
|
+
public async findUser(id: string) {
|
|
380
|
+
const result = await this.db.query("SELECT * FROM users WHERE id = $1", [
|
|
381
|
+
id,
|
|
382
|
+
]);
|
|
383
|
+
return result[0];
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Using ORM with Drizzle
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
import {
|
|
392
|
+
Column,
|
|
393
|
+
DrizzleBaseRepository,
|
|
394
|
+
Entity,
|
|
395
|
+
PrimaryKey,
|
|
396
|
+
Repository,
|
|
397
|
+
} from "@dangao/bun-server";
|
|
398
|
+
|
|
399
|
+
@Entity("users")
|
|
400
|
+
class User {
|
|
401
|
+
@PrimaryKey()
|
|
402
|
+
@Column({ type: "INTEGER", autoIncrement: true })
|
|
403
|
+
public id!: number;
|
|
404
|
+
|
|
405
|
+
@Column({ type: "TEXT", nullable: false })
|
|
406
|
+
public name!: string;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
@Repository("users", "id")
|
|
410
|
+
class UserRepository extends DrizzleBaseRepository<User> {}
|
|
411
|
+
|
|
412
|
+
@Injectable()
|
|
413
|
+
class UserService {
|
|
414
|
+
public constructor(
|
|
415
|
+
@Inject(UserRepository) private readonly repo: UserRepository,
|
|
416
|
+
) {}
|
|
417
|
+
|
|
418
|
+
public async findAll() {
|
|
419
|
+
return await this.repo.findAll();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Using Transactions
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
import { IsolationLevel, Propagation, Transactional } from "@dangao/bun-server";
|
|
428
|
+
|
|
429
|
+
@Injectable()
|
|
430
|
+
class OrderService {
|
|
431
|
+
@Transactional({
|
|
432
|
+
propagation: Propagation.REQUIRED,
|
|
433
|
+
isolationLevel: IsolationLevel.READ_COMMITTED,
|
|
434
|
+
})
|
|
435
|
+
public async createOrder(orderData: OrderData) {
|
|
436
|
+
// All database operations run in a transaction
|
|
437
|
+
await this.db.query("INSERT INTO orders ...");
|
|
438
|
+
await this.db.query("INSERT INTO order_items ...");
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## 10. Caching
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
import {
|
|
447
|
+
CACHE_SERVICE_TOKEN,
|
|
448
|
+
Cacheable,
|
|
449
|
+
CacheEvict,
|
|
450
|
+
CacheModule,
|
|
451
|
+
CacheService,
|
|
452
|
+
} from "@dangao/bun-server";
|
|
453
|
+
|
|
454
|
+
CacheModule.forRoot({
|
|
455
|
+
defaultTtl: 3600000, // 1 hour
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
@Injectable()
|
|
459
|
+
class ProductService {
|
|
460
|
+
public constructor(
|
|
461
|
+
@Inject(CACHE_SERVICE_TOKEN) private readonly cache: CacheService,
|
|
462
|
+
) {}
|
|
463
|
+
|
|
464
|
+
@Cacheable("product", 60000)
|
|
465
|
+
public async getProduct(id: string) {
|
|
466
|
+
// Expensive operation - result is cached
|
|
467
|
+
return await this.db.query("SELECT * FROM products WHERE id = $1", [id]);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
@CacheEvict("product")
|
|
471
|
+
public async updateProduct(id: string, data: ProductData) {
|
|
472
|
+
await this.db.query("UPDATE products ...");
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
## 11. Job Queue
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
import {
|
|
481
|
+
Cron,
|
|
482
|
+
Queue,
|
|
483
|
+
QUEUE_SERVICE_TOKEN,
|
|
484
|
+
QueueModule,
|
|
485
|
+
QueueService,
|
|
486
|
+
} from "@dangao/bun-server";
|
|
487
|
+
|
|
488
|
+
QueueModule.forRoot({
|
|
489
|
+
defaultRetries: 3,
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
@Injectable()
|
|
493
|
+
class EmailService {
|
|
494
|
+
@Queue("send-email")
|
|
495
|
+
public async sendEmail(data: { to: string; subject: string }) {
|
|
496
|
+
// Email sending logic
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
@Cron("0 0 * * *") // Daily at midnight
|
|
500
|
+
public async sendDailyReport() {
|
|
501
|
+
// Send daily report
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## 12. Session Management
|
|
507
|
+
|
|
508
|
+
```ts
|
|
509
|
+
import {
|
|
510
|
+
createSessionMiddleware,
|
|
511
|
+
Session,
|
|
512
|
+
SESSION_SERVICE_TOKEN,
|
|
513
|
+
SessionModule,
|
|
514
|
+
SessionService,
|
|
515
|
+
} from "@dangao/bun-server";
|
|
516
|
+
|
|
517
|
+
SessionModule.forRoot({
|
|
518
|
+
secret: "your-secret-key",
|
|
519
|
+
maxAge: 3600000, // 1 hour
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// Add session middleware
|
|
523
|
+
app.use(createSessionMiddleware());
|
|
524
|
+
|
|
525
|
+
@Controller("/api/auth")
|
|
526
|
+
class AuthController {
|
|
527
|
+
public constructor(
|
|
528
|
+
@Inject(SESSION_SERVICE_TOKEN) private readonly session: SessionService,
|
|
529
|
+
) {}
|
|
530
|
+
|
|
531
|
+
@POST("/login")
|
|
532
|
+
public async login(@Body() credentials: LoginDto, @Session() session: any) {
|
|
533
|
+
// Session is automatically injected
|
|
534
|
+
session.userId = credentials.userId;
|
|
535
|
+
return { success: true };
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
## 13. Health Checks
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
import { HealthModule } from "@dangao/bun-server";
|
|
544
|
+
|
|
545
|
+
HealthModule.forRoot({
|
|
546
|
+
indicators: [
|
|
547
|
+
{
|
|
548
|
+
name: "database",
|
|
549
|
+
check: async () => {
|
|
550
|
+
const isHealthy = await dbService.healthCheck();
|
|
551
|
+
return { status: isHealthy ? "up" : "down" };
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
],
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// Automatically provides /health and /ready endpoints
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
## 14. Metrics Collection
|
|
561
|
+
|
|
562
|
+
```ts
|
|
563
|
+
import {
|
|
564
|
+
createHttpMetricsMiddleware,
|
|
565
|
+
METRICS_SERVICE_TOKEN,
|
|
566
|
+
MetricsCollector,
|
|
567
|
+
MetricsModule,
|
|
568
|
+
} from "@dangao/bun-server";
|
|
569
|
+
|
|
570
|
+
MetricsModule.forRoot({
|
|
571
|
+
enableHttpMetrics: true,
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// Add metrics middleware
|
|
575
|
+
app.use(createHttpMetricsMiddleware());
|
|
576
|
+
|
|
577
|
+
@Injectable()
|
|
578
|
+
class OrderService {
|
|
579
|
+
public constructor(
|
|
580
|
+
@Inject(METRICS_SERVICE_TOKEN) private readonly metrics: MetricsCollector,
|
|
581
|
+
) {}
|
|
582
|
+
|
|
583
|
+
public async createOrder() {
|
|
584
|
+
this.metrics.increment("orders.created");
|
|
585
|
+
// Create order logic
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
## 15. Security and Authentication
|
|
591
|
+
|
|
592
|
+
```ts
|
|
593
|
+
import {
|
|
594
|
+
Auth,
|
|
595
|
+
SecurityContextHolder,
|
|
596
|
+
SecurityModule,
|
|
597
|
+
} from "@dangao/bun-server";
|
|
598
|
+
|
|
599
|
+
SecurityModule.forRoot({
|
|
600
|
+
jwt: {
|
|
601
|
+
secret: "your-secret-key",
|
|
602
|
+
accessTokenExpiresIn: 3600,
|
|
603
|
+
},
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
@Controller("/api/users")
|
|
607
|
+
class UserController {
|
|
608
|
+
@GET("/profile")
|
|
609
|
+
@Auth() // Require authentication
|
|
610
|
+
public getProfile() {
|
|
611
|
+
const context = SecurityContextHolder.getContext();
|
|
612
|
+
return context.getPrincipal();
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
@GET("/admin")
|
|
616
|
+
@Auth({ roles: ["admin"] }) // Require admin role
|
|
617
|
+
public getAdmin() {
|
|
618
|
+
return { message: "Admin access" };
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
## 16. Testing Recommendations
|
|
624
|
+
|
|
625
|
+
- Use `tests/utils/test-port.ts` to get auto-incrementing ports, avoiding local
|
|
626
|
+
conflicts.
|
|
627
|
+
- Call `RouteRegistry.getInstance().clear()` and
|
|
628
|
+
`ControllerRegistry.getInstance().clear()` in `afterEach` hooks to keep global
|
|
629
|
+
state clean.
|
|
630
|
+
- In end-to-end tests, you can directly instantiate `Context` and call
|
|
631
|
+
`router.handle(context)` without actually starting the server.
|
|
632
|
+
- For database integration tests, use environment variables (`POSTGRES_URL`,
|
|
633
|
+
`MYSQL_URL`) to configure connections, tests will automatically skip if not
|
|
634
|
+
configured.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Migration Guide (English Draft)
|
|
2
|
+
|
|
3
|
+
A full translation of `docs/migration.md` is in progress. Planned sections:
|
|
4
|
+
|
|
5
|
+
- Migrating from Express / Koa / NestJS
|
|
6
|
+
- Moving from Node.js to Bun
|
|
7
|
+
- Updating legacy middleware
|
|
8
|
+
- Breaking changes per release
|
|
9
|
+
|
|
10
|
+
Please consult the Chinese version for now. If you would like to help, open an issue or PR.
|