shokupan 0.11.0 → 0.12.0

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.
Files changed (48) hide show
  1. package/README.md +46 -1815
  2. package/dist/{analyzer-CnKnQ5KV.js → analyzer-BkNQHWj4.js} +2 -2
  3. package/dist/{analyzer-CnKnQ5KV.js.map → analyzer-BkNQHWj4.js.map} +1 -1
  4. package/dist/{analyzer-BAhvpNY_.cjs → analyzer-DM-OlRq8.cjs} +2 -2
  5. package/dist/{analyzer-BAhvpNY_.cjs.map → analyzer-DM-OlRq8.cjs.map} +1 -1
  6. package/dist/{analyzer.impl-CfpMu4-g.cjs → analyzer.impl-CVJ8zfGQ.cjs} +11 -3
  7. package/dist/analyzer.impl-CVJ8zfGQ.cjs.map +1 -0
  8. package/dist/{analyzer.impl-DCiqlXI5.js → analyzer.impl-CsA1bS_s.js} +11 -3
  9. package/dist/analyzer.impl-CsA1bS_s.js.map +1 -0
  10. package/dist/cli.cjs +1 -1
  11. package/dist/cli.js +1 -1
  12. package/dist/context.d.ts +40 -8
  13. package/dist/index.cjs +1011 -300
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.js +1011 -300
  16. package/dist/index.js.map +1 -1
  17. package/dist/plugins/application/api-explorer/static/theme.css +4 -0
  18. package/dist/plugins/application/auth.d.ts +5 -0
  19. package/dist/plugins/application/dashboard/fetch-interceptor.d.ts +12 -0
  20. package/dist/plugins/application/dashboard/plugin.d.ts +9 -0
  21. package/dist/plugins/application/dashboard/static/requests.js +537 -251
  22. package/dist/plugins/application/dashboard/static/tabulator.css +23 -3
  23. package/dist/plugins/application/dashboard/static/theme.css +4 -0
  24. package/dist/plugins/application/mcp-server/plugin.d.ts +39 -0
  25. package/dist/plugins/application/openapi/analyzer.impl.d.ts +4 -0
  26. package/dist/plugins/middleware/compression.d.ts +12 -2
  27. package/dist/plugins/middleware/rate-limit.d.ts +5 -0
  28. package/dist/router.d.ts +6 -5
  29. package/dist/server.d.ts +22 -0
  30. package/dist/shokupan.d.ts +24 -1
  31. package/dist/util/adapter/bun.d.ts +8 -0
  32. package/dist/util/adapter/index.d.ts +4 -0
  33. package/dist/util/adapter/interface.d.ts +12 -0
  34. package/dist/util/adapter/node.d.ts +8 -0
  35. package/dist/util/adapter/wintercg.d.ts +5 -0
  36. package/dist/util/body-parser.d.ts +30 -0
  37. package/dist/util/decorators.d.ts +20 -3
  38. package/dist/util/di.d.ts +3 -8
  39. package/dist/util/metadata.d.ts +18 -0
  40. package/dist/util/request.d.ts +1 -0
  41. package/dist/util/symbol.d.ts +1 -0
  42. package/dist/util/types.d.ts +132 -3
  43. package/package.json +3 -1
  44. package/dist/analyzer.impl-CfpMu4-g.cjs.map +0 -1
  45. package/dist/analyzer.impl-DCiqlXI5.js.map +0 -1
  46. package/dist/plugins/application/dashboard/static/failures.js +0 -85
  47. package/dist/plugins/application/http-server.d.ts +0 -13
  48. package/dist/util/adapter/adapters.d.ts +0 -19
package/README.md CHANGED
@@ -5,7 +5,8 @@
5
5
  **Built for Developer Experience**
6
6
  Shokupan is designed to make building APIs delightful again. With zero-config defaults, instant startup times, and full type safety out of the box, you can focus on building your product, not configuring your framework.
7
7
 
8
- ### Note: Shokupan is still in alpha and is not guaranteed to be stable. Please use with caution. We will be adding more features and APIs in the future. Please file an issue if you find any bugs or have suggestions for improvement.
8
+ > [!CAUTION]
9
+ > Shokupan is still in alpha and is not guaranteed to be stable. Please use with caution. We will be adding more features and APIs in the future. Please file an issue if you find any bugs or have suggestions for improvement.
9
10
 
10
11
  📚 **[Full documentation available at https://shokupan.dev](https://shokupan.dev)**
11
12
 
@@ -25,7 +26,7 @@ Shokupan is designed to make building APIs delightful again. With zero-config de
25
26
  - 📚 **OpenAPI Docs** - Beautiful OpenAPI documentation with [Scalar](https://scalar.dev/).
26
27
  - ⏩ **Short shift** - Very simple migration from [Express](https://expressjs.com/) or [NestJS](https://nestjs.com/) to Shokupan.
27
28
 
28
- ![Shokupan Debug Dashboard](docs/src/assets/debug_dashboard_overview.png)
29
+ ![Shokupan Debug Dashboard](docs/src/assets/dashboard_charts_1.png)
29
30
 
30
31
 
31
32
  ## 🚀 Quick Start
@@ -48,1829 +49,59 @@ app.listen();
48
49
 
49
50
  That's it! Your server is running at `http://localhost:3000` 🎉
50
51
 
51
- ## 📖 Table of Contents
52
+ ## 💡 Core Concepts
52
53
 
53
- - [Core Concepts](#core-concepts)
54
- - [Routing](#routing)
55
- - [Controllers](#controllers)
56
- - [Middleware](#middleware)
57
- - [Context](#context)
58
- - [Static Files](#static-files)
59
- - [Plugins](#plugins)
60
- - [CORS](#cors)
61
- - [Compression](#compression)
62
- - [Rate Limiting](#rate-limiting)
63
- - [Security Headers](#security-headers)
64
- - [Sessions](#sessions)
65
- - [Authentication](#authentication)
66
- - [Validation](#validation)
67
- - [Scalar (OpenAPI)](#scalar-openapi)
68
- - [Advanced Features](#advanced-features)
69
- - [Dependency Injection](#dependency-injection)
70
- - [OpenAPI Generation](#openapi-generation)
71
- - [Sub-Requests](#sub-requests)
72
- - [OpenTelemetry](#opentelemetry)
73
- - [Migration Guides](#migration-guides)
74
- - [From Express](#from-express)
75
- - [From Koa](#from-koa)
76
- - [From NestJS](#from-nestjs)
77
- - [Using Express Middleware](#using-express-middleware)
78
- - [Testing](#testing)
79
- - [Deployment](#deployment)
80
- - [Production Best Practices](https://knackstedt.github.io/shokupan/guides/production/) 📚
81
- - [CLI Tools](#cli-tools)
82
- - [API Reference](#api-reference)
83
- - [Roadmap](#-roadmap)
54
+ Shokupan provides a familiar yet modern API.
84
55
 
85
- ## Core Concepts
86
-
87
- ### Routing
88
-
89
- Shokupan supports Express-style routing with a clean, intuitive API:
90
-
91
- #### Basic Routes
92
-
93
- ```typescript
94
- import { Shokupan } from 'shokupan';
95
-
96
- const app = new Shokupan();
97
-
98
- // GET request
99
- app.get('/users', (ctx) => {
100
- return { users: ['Alice', 'Bob'] };
101
- });
102
-
103
- // POST request
104
- app.post('/users', async (ctx) => {
105
- const body = await ctx.body();
106
- return { created: body };
107
- });
108
-
109
- // PUT, PATCH, DELETE
110
- app.put('/users/:id', (ctx) => ({ updated: ctx.params.id }));
111
- app.patch('/users/:id', (ctx) => ({ patched: ctx.params.id }));
112
- app.delete('/users/:id', (ctx) => ({ deleted: ctx.params.id }));
113
-
114
- app.listen();
115
- ```
116
-
117
- #### Path Parameters
118
-
119
- ```typescript
120
- app.get('/users/:id', (ctx) => {
121
- const userId = ctx.params.id;
122
- return { id: userId, name: 'Alice' };
123
- });
124
-
125
- app.get('/posts/:postId/comments/:commentId', (ctx) => {
126
- return {
127
- postId: ctx.params.postId,
128
- commentId: ctx.params.commentId
129
- };
130
- });
131
- ```
132
-
133
- #### Query Strings
134
-
135
- ```typescript
136
- app.get('/search', (ctx) => {
137
- const query = ctx.query.get('q');
138
- const page = ctx.query.get('page') || '1';
139
-
140
- return {
141
- query,
142
- page: parseInt(page),
143
- results: []
144
- };
145
- });
146
-
147
- // GET /search?q=shokupan&page=2
148
- ```
149
-
150
- #### Routers
151
-
152
- ```typescript
153
- import { ShokupanRouter } from 'shokupan';
154
-
155
- const apiRouter = new ShokupanRouter();
156
-
157
- apiRouter.get('/users', (ctx) => ({ users: [] }));
158
- apiRouter.get('/posts', (ctx) => ({ posts: [] }));
159
-
160
- // Mount router under /api prefix
161
- app.mount('/api', apiRouter);
162
-
163
- // Available at:
164
- // GET /api/users
165
- // GET /api/posts
166
- ```
167
-
168
- ### Controllers
169
-
170
- Use decorators for a more structured, class-based approach:
171
-
172
- <!-- @Controller('/users') -->
173
- ```typescript
174
- import { Controller, Get, Post, Put, Delete, Param, Body, Query } from 'shokupan';
175
-
176
- export class UserController {
177
-
178
- @Get('/')
179
- async getUsers(@Query('role') role?: string) {
180
- return {
181
- users: ['Alice', 'Bob'],
182
- filter: role || 'all'
183
- };
184
- }
185
-
186
- @Get('/:id')
187
- async getUserById(@Param('id') id: string) {
188
- return {
189
- id,
190
- name: 'Alice',
191
- email: 'alice@example.com'
192
- };
193
- }
194
-
195
- @Post('/')
196
- async createUser(@Body() body: any) {
197
- return {
198
- message: 'User created',
199
- data: body
200
- };
201
- }
202
-
203
- @Put('/:id')
204
- async updateUser(
205
- @Param('id') id: string,
206
- @Body() body: any
207
- ) {
208
- return {
209
- message: 'User updated',
210
- id,
211
- data: body
212
- };
213
- }
214
-
215
- @Delete('/:id')
216
- async deleteUser(@Param('id') id: string) {
217
- return { message: 'User deleted', id };
218
- }
219
- }
220
-
221
- // Mount the controller
222
- app.mount('/api', UserController);
223
- ```
224
-
225
- #### Available Decorators
226
-
227
- <!-- - `@Controller(path)` - Define base path for controller -->
228
- - `@Get(path)` - GET route
229
- - `@Post(path)` - POST route
230
- - `@Put(path)` - PUT route
231
- - `@Patch(path)` - PATCH route
232
- - `@Delete(path)` - DELETE route
233
- - `@Options(path)` - OPTIONS route
234
- - `@Head(path)` - HEAD route
235
- - `@All(path)` - Match all HTTP methods
236
-
237
- **Parameter Decorators:**
238
-
239
- - `@Param(name)` - Extract path parameter
240
- - `@Query(name)` - Extract query parameter
241
- - `@Body()` - Parse request body
242
- - `@Headers(name)` - Extract header
243
- - `@Ctx()` - Access full context
244
- - `@Req()` - Access request object
245
-
246
- ### WebSockets
247
-
248
- > Notice: The WebSocket API is currently in an experimental stage and subject to change.
249
-
250
- Shokupan provides native WebSocket handling and an HTTP Bridge feature.
251
-
252
- #### Event Decorator
253
-
254
- Handle WebSocket events directly in your controllers:
255
-
256
- ```typescript
257
- import { Controller, Event, ShokupanContext } from 'shokupan';
258
-
259
- @Controller('/chat')
260
- export class ChatController {
261
-
262
- @Event('message')
263
- async onMessage(ctx: ShokupanContext) {
264
- const payload = await ctx.body();
265
- console.log('Received:', payload);
266
-
267
- // Reply using underlying socket
268
- // Native Bun: ctx.socket is ServerWebSocket
269
- // Socket.IO: ctx.socket is Socket
270
- ctx.emit('ack', 'received');
271
- }
272
- }
273
- ```
274
-
275
- #### Socket.IO Support
276
-
277
- Easily integrate Socket.IO and wire up your event decorators via the built-in helper:
278
-
279
- ```typescript
280
- import { Shokupan } from 'shokupan';
281
- import { Server } from 'socket.io';
282
- import { attachSocketIOBridge } from 'shokupan';
283
-
284
- const app = new Shokupan({ enableHttpBridge: false });
285
- const server = await app.listen(3000);
286
-
287
- // Attach Socket.IO
288
- const io = new Server(server.nodeServer); // For Node.js
289
- attachSocketIOBridge(io, app);
290
- ```
291
-
292
- #### HTTP Bridge
293
-
294
- Expose your HTTP API over WebSockets (set `enableHttpBridge: true` in config).
295
-
296
- Client sends: `{ type: "HTTP", method: "GET", path: "/api/users", ... }`
297
- Server responds: `{ type: "RESPONSE", status: 200, body: ... }`
298
-
299
- ### Middleware
300
-
301
- Middleware functions have access to the context and can control request flow:
302
-
303
- ```typescript
304
- import { Middleware } from 'shokupan';
305
-
306
- // Simple logging middleware
307
- const logger: Middleware = async (ctx, next) => {
308
- console.log(`${ctx.method} ${ctx.path}`);
309
- const start = Date.now();
310
-
311
- const result = await next();
312
-
313
- console.log(`${ctx.method} ${ctx.path} - ${Date.now() - start}ms`);
314
- return result;
315
- };
316
-
317
- app.use(logger);
318
-
319
- // Authentication middleware
320
- const auth: Middleware = async (ctx, next) => {
321
- const token = ctx.headers.get('Authorization');
322
-
323
- if (!token) {
324
- return ctx.json({ error: 'Unauthorized' }, 401);
325
- }
326
-
327
- // Validate token and attach user to state
328
- ctx.state.user = { id: '123', name: 'Alice' };
329
-
330
- return next();
331
- };
332
-
333
- // Apply to specific routes
334
- app.get('/protected', auth, (ctx) => {
335
- return { user: ctx.state.user };
336
- });
337
- ```
338
-
339
- Or use with decorators:
340
- ```ts
341
- import { Use } from 'shokupan';
342
-
343
- @Controller('/admin')
344
- @Use(auth) // Apply to all routes in controller
345
- export class AdminController {
346
-
347
- @Get('/dashboard')
348
- getDashboard(@Ctx() ctx) {
349
- return { user: ctx.state.user };
350
- }
351
- }
352
- ```
353
-
354
- ### Context
355
-
356
- The `ShokupanContext` provides a rich API for handling requests and responses:
357
-
358
- ```typescript
359
- app.get('/demo', async (ctx) => {
360
- // Request properties
361
- ctx.method; // HTTP method
362
- ctx.path; // URL path
363
- ctx.url; // Full URL
364
- ctx.params; // Path parameters
365
- ctx.query; // Query string (URLSearchParams)
366
- ctx.headers; // Headers (Headers object)
367
-
368
- // Request body
369
- const body = await ctx.body(); // Auto-parsed JSON/form/multipart
370
- const json = await ctx.req.json(); // JSON body
371
- const text = await ctx.req.text(); // Text body
372
- const form = await ctx.req.formData(); // Form data
373
-
374
- // State (shared across middleware)
375
- ctx.state.user = { id: '123' };
376
-
377
- // Response helpers
378
- return ctx.json({ message: 'Hello' }); // JSON response
379
- return ctx.text('Hello World'); // Text response
380
- return ctx.html('<h1>Hello</h1>'); // HTML response
381
- return ctx.redirect('/new-path'); // Redirect
382
-
383
- // Set response headers
384
- ctx.set('X-Custom-Header', 'value');
385
-
386
- // Set cookies
387
- ctx.setCookie('session', 'abc123', {
388
- httpOnly: true,
389
- secure: true,
390
- maxAge: 3600
391
- });
392
-
393
- // Return Response directly
394
- return new Response('Custom response', {
395
- status: 200,
396
- headers: { 'Content-Type': 'text/plain' }
397
- });
398
- });
399
- ```
400
-
401
- ### Static Files
402
-
403
- Serve static files with directory listing support:
404
-
405
- ```typescript
406
- // Serve static files from a directory
407
- app.static('/public', {
408
- root: './public',
409
- listDirectory: true // Enable directory listing
410
- });
411
-
412
- // Multiple static directories
413
- app.static('/images', {
414
- root: './assets/images',
415
- listDirectory: true
416
- });
417
-
418
- app.static('/js', {
419
- root: './assets/js',
420
- listDirectory: false
421
- });
422
-
423
- // Files available at:
424
- // GET /public/style.css -> ./public/style.css
425
- // GET /images/logo.png -> ./assets/images/logo.png
426
- ```
56
+ - **[Routing](https://shokupan.dev/core/routing)**: Express-style routing (`app.get`, `app.post`) with a clean, intuitive API.
57
+ - **[Controllers](https://shokupan.dev/core/controllers)**: Decorator-based controllers (`@Controller`, `@Get`) for structured applications.
58
+ - **[Middleware](https://shokupan.dev/core/middleware)**: Koa-style async middleware for request processing and flow control.
59
+ - **[Context](https://shokupan.dev/core/context)**: A rich `ctx` object containing request, response, parameters, and shared state.
60
+ - **[Static Files](https://shokupan.dev/core/static-files)**: Serve static assets with ease.
61
+ - **[WebSockets](https://shokupan.dev/core/websockets)**: Native WebSocket handling and HTTP Bridge feature.
427
62
 
428
63
  ## 🔌 Plugins
429
64
 
430
- ### CORS
431
-
432
- Configure Cross-Origin Resource Sharing:
433
-
434
- ```typescript
435
- import { Cors } from 'shokupan';
436
-
437
- // Simple CORS - allow all origins
438
- app.use(Cors());
439
-
440
- // Custom configuration
441
- app.use(Cors({
442
- origin: 'https://example.com',
443
- methods: ['GET', 'POST', 'PUT'],
444
- credentials: true,
445
- maxAge: 86400
446
- }));
447
-
448
- // Multiple origins
449
- app.use(Cors({
450
- origin: ['https://example.com', 'https://app.example.com'],
451
- credentials: true
452
- }));
453
-
454
- // Dynamic origin validation
455
- app.use(Cors({
456
- origin: (ctx) => {
457
- const origin = ctx.headers.get('origin');
458
- // Validate origin dynamically
459
- return origin?.endsWith('.example.com') ? origin : false;
460
- },
461
- credentials: true
462
- }));
463
-
464
- // Full options
465
- app.use(Cors({
466
- origin: '*', // or string, string[], function
467
- methods: 'GET,POST,PUT,DELETE', // or string[]
468
- allowedHeaders: ['Content-Type'], // or string
469
- exposedHeaders: ['X-Total-Count'], // or string
470
- credentials: true,
471
- maxAge: 86400 // Preflight cache duration
472
- }));
473
- ```
474
-
475
- ### Compression
476
-
477
- Enable response compression:
478
-
479
- ```typescript
480
- import { Compression } from 'shokupan';
481
-
482
- // Simple compression
483
- app.use(Compression());
484
-
485
- // Custom configuration
486
- app.use(Compression({
487
- threshold: 1024, // Only compress responses larger than 1KB
488
- level: 6 // Compression level (1-9)
489
- }));
490
- ```
491
-
492
- ### Rate Limiting
493
-
494
- Protect your API from abuse:
495
-
496
- ```typescript
497
- import { RateLimit } from 'shokupan';
498
-
499
- // Basic rate limiting - 100 requests per 15 minutes
500
- app.use(RateLimit({
501
- windowMs: 15 * 60 * 1000,
502
- max: 100
503
- }));
504
-
505
- // Different limits for different routes
506
- const apiLimiter = RateLimit({
507
- windowMs: 15 * 60 * 1000,
508
- max: 100,
509
- message: 'Too many requests from this IP'
510
- });
511
-
512
- const authLimiter = RateLimit({
513
- windowMs: 15 * 60 * 1000,
514
- max: 5,
515
- message: 'Too many login attempts'
516
- });
517
-
518
- app.use('/api', apiLimiter);
519
- app.use('/auth/login', authLimiter);
520
-
521
- // Custom key generator
522
- app.use(RateLimit({
523
- windowMs: 15 * 60 * 1000,
524
- max: 100,
525
- keyGenerator: (ctx) => {
526
- // Rate limit by user ID instead of IP
527
- return ctx.state.user?.id || ctx.ip;
528
- }
529
- }));
530
- ```
531
-
532
- ### Security Headers
533
-
534
- Add security headers to responses:
535
-
536
- ```typescript
537
- import { SecurityHeaders } from 'shokupan';
538
-
539
- // Default secure headers
540
- app.use(SecurityHeaders());
541
-
542
- // Custom configuration
543
- app.use(SecurityHeaders({
544
- contentSecurityPolicy: {
545
- directives: {
546
- defaultSrc: ["'self'"],
547
- styleSrc: ["'self'", "'unsafe-inline'"],
548
- scriptSrc: ["'self'", "https://trusted-cdn.com"],
549
- imgSrc: ["'self'", "data:", "https:"]
550
- }
551
- },
552
- hsts: {
553
- maxAge: 31536000,
554
- includeSubDomains: true,
555
- preload: true
556
- },
557
- frameguard: {
558
- action: 'deny'
559
- }
560
- }));
561
- ```
562
-
563
- ### Sessions
564
-
565
- Session management with connect-style store support:
566
-
567
- ```typescript
568
- import { Session } from 'shokupan';
569
-
570
- // Basic session with memory store (development only)
571
- app.use(Session({
572
- secret: 'your-secret-key'
573
- }));
574
-
575
- // Full configuration
576
- app.use(Session({
577
- secret: 'your-secret-key',
578
- name: 'sessionId', // Cookie name
579
- resave: false, // Don't save unchanged sessions
580
- saveUninitialized: false, // Don't create sessions until needed
581
- cookie: {
582
- httpOnly: true,
583
- secure: true, // HTTPS only
584
- maxAge: 24 * 60 * 60 * 1000, // 24 hours
585
- sameSite: 'lax'
586
- }
587
- }));
588
-
589
- // Use session in routes
590
- app.get('/login', async (ctx) => {
591
- ctx.session.user = { id: '123', name: 'Alice' };
592
- return { message: 'Logged in' };
593
- });
594
-
595
- app.get('/profile', (ctx) => {
596
- if (!ctx.session.user) {
597
- return ctx.json({ error: 'Not authenticated' }, 401);
598
- }
599
- return ctx.session.user;
600
- });
601
-
602
- app.get('/logout', (ctx) => {
603
- ctx.session.destroy();
604
- return { message: 'Logged out' };
605
- });
606
- ```
607
-
608
- #### Using Connect-Style Session Stores
609
-
610
- Shokupan is compatible with connect/express-session stores:
611
-
612
- ```typescript
613
- import { Session } from 'shokupan';
614
- import RedisStore from 'connect-redis';
615
- import { createClient } from 'redis';
616
-
617
- // Redis session store
618
- const redisClient = createClient();
619
- await redisClient.connect();
620
-
621
- app.use(Session({
622
- secret: 'your-secret-key',
623
- store: new RedisStore({ client: redisClient }),
624
- cookie: {
625
- maxAge: 24 * 60 * 60 * 1000
626
- }
627
- }));
628
- ```
629
-
630
- Compatible stores include:
631
- - `connect-redis` - Redis
632
- - `connect-mongo` - MongoDB
633
- - `connect-sqlite3` - SQLite
634
- - `session-file-store` - File system
635
- - Any connect-compatible session store
636
-
637
- ### Authentication
638
-
639
- Built-in OAuth2 support with multiple providers:
640
-
641
- ```typescript
642
- import { AuthPlugin } from 'shokupan';
643
-
644
- const auth = new AuthPlugin({
645
- jwtSecret: 'your-jwt-secret',
646
- jwtExpiration: '7d',
647
-
648
- // Cookie configuration
649
- cookieOptions: {
650
- httpOnly: true,
651
- secure: true,
652
- sameSite: 'lax'
653
- },
654
-
655
- // GitHub OAuth
656
- github: {
657
- clientId: process.env.GITHUB_CLIENT_ID!,
658
- clientSecret: process.env.GITHUB_CLIENT_SECRET!,
659
- redirectUri: 'http://localhost:3000/auth/github/callback'
660
- },
661
-
662
- // Google OAuth
663
- google: {
664
- clientId: process.env.GOOGLE_CLIENT_ID!,
665
- clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
666
- redirectUri: 'http://localhost:3000/auth/google/callback'
667
- },
668
-
669
- // Microsoft OAuth
670
- microsoft: {
671
- clientId: process.env.MICROSOFT_CLIENT_ID!,
672
- clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
673
- redirectUri: 'http://localhost:3000/auth/microsoft/callback',
674
- tenantId: 'common'
675
- },
676
-
677
- // Apple OAuth
678
- apple: {
679
- clientId: process.env.APPLE_CLIENT_ID!,
680
- clientSecret: process.env.APPLE_CLIENT_SECRET!,
681
- redirectUri: 'http://localhost:3000/auth/apple/callback',
682
- teamId: process.env.APPLE_TEAM_ID!,
683
- keyId: process.env.APPLE_KEY_ID!
684
- },
685
-
686
- // Auth0
687
- auth0: {
688
- clientId: process.env.AUTH0_CLIENT_ID!,
689
- clientSecret: process.env.AUTH0_CLIENT_SECRET!,
690
- redirectUri: 'http://localhost:3000/auth/auth0/callback',
691
- domain: 'your-tenant.auth0.com'
692
- },
693
-
694
- // Okta
695
- okta: {
696
- clientId: process.env.OKTA_CLIENT_ID!,
697
- clientSecret: process.env.OKTA_CLIENT_SECRET!,
698
- redirectUri: 'http://localhost:3000/auth/okta/callback',
699
- domain: 'your-domain.okta.com'
700
- },
701
-
702
- // Custom OAuth2
703
- oauth2: {
704
- clientId: 'your-client-id',
705
- clientSecret: 'your-client-secret',
706
- redirectUri: 'http://localhost:3000/auth/custom/callback',
707
- authUrl: 'https://provider.com/oauth/authorize',
708
- tokenUrl: 'https://provider.com/oauth/token',
709
- userInfoUrl: 'https://provider.com/oauth/userinfo'
710
- }
711
- });
712
-
713
- // Mount auth routes at /auth
714
- app.mount('/auth', auth);
715
-
716
- // Protect routes with auth middleware
717
- app.get('/protected', auth.middleware(), (ctx) => {
718
- return { user: ctx.state.user };
719
- });
720
-
721
- // Available auth routes:
722
- // GET /auth/github
723
- // GET /auth/github/callback
724
- // GET /auth/google
725
- // GET /auth/google/callback
726
- // ... (and all other providers)
727
- ```
728
-
729
- ### Validation
730
-
731
- Validate request data with your favorite validation library:
732
-
733
- ```typescript
734
- import { validate } from 'shokupan';
735
- import { z } from 'zod';
736
-
737
- // Zod validation
738
- const userSchema = z.object({
739
- name: z.string().min(2),
740
- email: z.string().email(),
741
- age: z.number().min(18)
742
- });
743
-
744
- app.post('/users',
745
- validate({ body: userSchema }),
746
- async (ctx) => {
747
- const body = await ctx.body(); // Already validated!
748
- return { created: body };
749
- }
750
- );
751
-
752
- // Validate query parameters
753
- const searchSchema = z.object({
754
- q: z.string(),
755
- page: z.coerce.number().default(1),
756
- limit: z.coerce.number().max(100).default(10)
757
- });
758
-
759
- app.get('/search',
760
- validate({ query: searchSchema }),
761
- (ctx) => {
762
- const q = ctx.query.get('q');
763
- const page = ctx.query.get('page');
764
- return { q, page };
765
- }
766
- );
767
-
768
- // Validate path parameters
769
- app.get('/users/:id',
770
- validate({
771
- params: z.object({
772
- id: z.string().uuid()
773
- })
774
- }),
775
- (ctx) => {
776
- return { id: ctx.params.id };
777
- }
778
- );
779
-
780
- // Validate headers
781
- app.post('/webhook',
782
- validate({
783
- headers: z.object({
784
- 'x-webhook-signature': z.string()
785
- })
786
- }),
787
- async (ctx) => {
788
- // Process webhook
789
- }
790
- );
791
- ```
792
-
793
- #### TypeBox Validation
794
-
795
- ```typescript
796
- import { Type } from '@sinclair/typebox';
797
- import { validate } from 'shokupan';
798
-
799
- const UserSchema = Type.Object({
800
- name: Type.String({ minLength: 2 }),
801
- email: Type.String({ format: 'email' }),
802
- age: Type.Number({ minimum: 18 })
803
- });
804
-
805
- app.post('/users',
806
- validate({ body: UserSchema }),
807
- async (ctx) => {
808
- const user = await ctx.body();
809
- return { created: user };
810
- }
811
- );
812
- ```
813
-
814
- #### Ajv Validation
815
-
816
- ```typescript
817
- import Ajv from 'ajv';
818
- import { validate } from 'shokupan';
819
-
820
- const ajv = new Ajv();
821
- const userSchema = ajv.compile({
822
- type: 'object',
823
- properties: {
824
- name: { type: 'string', minLength: 2 },
825
- email: { type: 'string', format: 'email' },
826
- age: { type: 'number', minimum: 18 }
827
- },
828
- required: ['name', 'email', 'age']
829
- });
830
-
831
- app.post('/users',
832
- validate({ body: userSchema }),
833
- async (ctx) => {
834
- const user = await ctx.body();
835
- return { created: user };
836
- }
837
- );
838
- ```
839
-
840
- #### Valibot Validation
841
-
842
- ```typescript
843
- import * as v from 'valibot';
844
- import { validate, valibot } from 'shokupan';
845
-
846
- const UserSchema = v.object({
847
- name: v.pipe(v.string(), v.minLength(2)),
848
- email: v.pipe(v.string(), v.email()),
849
- age: v.pipe(v.number(), v.minValue(18))
850
- });
851
-
852
- app.post('/users',
853
- validate({
854
- body: valibot(UserSchema, v.parseAsync)
855
- }),
856
- async (ctx) => {
857
- const user = await ctx.body();
858
- return { created: user };
859
- }
860
- );
861
- ```
862
-
863
- ### Scalar (OpenAPI)
864
-
865
- Beautiful, interactive API documentation:
866
-
867
- ```typescript
868
- import { ScalarPlugin } from 'shokupan';
869
-
870
- app.mount('/docs', new ScalarPlugin({
871
- baseDocument: {
872
- info: {
873
- title: 'My API',
874
- version: '1.0.0',
875
- description: 'API documentation'
876
- }
877
- },
878
- config: {
879
- theme: 'purple',
880
- layout: 'modern'
881
- }
882
- }));
883
-
884
- // Access docs at http://localhost:3000/docs
885
- ```
886
-
887
- The Scalar plugin automatically generates OpenAPI documentation from your routes and controllers!
888
-
889
- ### Proxy
890
-
891
- Create a reverse proxy to forward requests to another server:
892
-
893
- ```typescript
894
- import { Proxy } from 'shokupan';
895
-
896
- app.use('/api/v1', Proxy({
897
- target: 'https://api.example.com',
898
- changeOrigin: true,
899
- pathRewrite: (path) => path.replace('/api/v1', ''),
900
- headers: {
901
- 'X-Custom-Header': 'Proxy'
902
- }
903
- }));
904
-
905
- // Proxy WebSockets
906
- app.use('/socket', Proxy({
907
- target: 'ws://ws.example.com',
908
- ws: true
909
- }));
910
- ```
911
-
912
- ### OpenAPI Validator
913
-
914
- Validate incoming requests against your generated OpenAPI specification:
915
-
916
- ```typescript
917
- import { enableOpenApiValidation } from 'shokupan';
918
-
919
- const app = new Shokupan({ enableOpenApiGen: true });
920
-
921
- // Enable validation middleware
922
- // This validates Body, Query, Params, and Headers against your OpenAPI definitions
923
- enableOpenApiValidation(app);
924
-
925
- app.post('/users', {
926
- parameters: [
927
- { name: 'apiKey', in: 'header', required: true, schema: { type: 'string' } }
928
- ],
929
- requestBody: {
930
- content: {
931
- 'application/json': {
932
- schema: {
933
- type: 'object',
934
- required: ['name'],
935
- properties: {
936
- name: { type: 'string', minLength: 3 }
937
- }
938
- }
939
- }
940
- }
941
- }
942
- }, (ctx) => {
943
- return { success: true };
944
- });
945
-
946
- // Invalid requests will throw a ValidationError (400 Bad Request)
947
- ```
948
-
949
- ### Idempotency
950
-
951
- Ensure that multiple identical requests do not result in different outcomes (e.g., duplicate payments). This middleware caches the response of the first request and returns it for subsequent requests with the same idempotency key.
952
-
953
- ```typescript
954
- import { Idempotency } from 'shokupan';
955
-
956
- app.post('/payments',
957
- Idempotency({
958
- header: 'Idempotency-Key', // default
959
- ttl: 24 * 60 * 60 * 1000 // default 24h
960
- }),
961
- async (ctx) => {
962
- // Process payment...
963
- return { status: 'charged' };
964
- }
965
- );
966
- ```
967
-
968
- ### Failed Request Recorder
969
-
970
- Automatically record failed requests (500s) for debugging and replay purposes.
971
-
972
- ```typescript
973
- import { FailedRequestRecorder } from 'shokupan';
974
-
975
- app.use(FailedRequestRecorder({
976
- maxCapacity: 1000,
977
- ttl: 86400000 // 1 day
978
- }));
979
- ```
980
-
981
- This works great when combined with the Debug Dashboard.
982
-
983
- ### Debug Dashboard
984
-
985
- A visual dashboard to inspect your application, view metrics, analyze the middleware graph, and replay failed requests.
986
-
987
- ```typescript
988
- import { Dashboard } from 'shokupan';
989
-
990
- // Mount the dashboard
991
- app.mount('/debug', new Dashboard({
992
- retentionMs: 2 * 60 * 60 * 1000, // Keep 2 hours of logs
993
- getRequestHeaders: () => ({
994
- 'Authorization': 'Bearer ...' // Headers to using when replaying requests and accessing data APIs
995
- })
996
- }));
997
-
998
- // Available at http://localhost:3000/debug
999
- ```
1000
-
65
+ Shokupan has a rich ecosystem of plugins.
66
+
67
+ | Plugin | Description |
68
+ | :--- | :--- |
69
+ | **[Dashboard](https://shokupan.dev/plugins/dashboard)** | Visual dashboard for debugging and analysis. |
70
+ | **[CORS](https://shokupan.dev/plugins/cors)** | Configure Cross-Origin Resource Sharing. |
71
+ | **[Compression](https://shokupan.dev/plugins/compression)** | Enable response compression (gzip, deflate, etc.). |
72
+ | **[Rate Limiting](https://shokupan.dev/plugins/rate-limiting)** | Protect your API from abuse. |
73
+ | **[Security Headers](https://shokupan.dev/plugins/security-headers)** | Add essential security headers (CSP, HSTS, etc.). |
74
+ | **[Sessions](https://shokupan.dev/plugins/sessions)** | Session management with connect-style store support. |
75
+ | **[Authentication](https://shokupan.dev/plugins/authentication)** | Built-in OAuth2 support (GitHub, Google, etc.). |
76
+ | **[Validation](https://shokupan.dev/plugins/validation)** | Validate requests with Zod, Ajv, TypeBox, or Valibot. |
77
+ | **[Scalar (OpenAPI)](https://shokupan.dev/plugins/scalar)** | Beautiful, interactive API documentation. |
78
+ | **[API Explorer](https://shokupan.dev/plugins/api-explorer)** | Built-in interactive documentation for your API. |
79
+ | **[AsyncAPI](https://shokupan.dev/plugins/asyncapi)** | Generate and view documentation for WebSocket APIs. |
80
+ | **[Cluster](https://shokupan.dev/plugins/cluster)** | Utilize multiple CPU cores for better performance. |
81
+ | **[GraphQL](https://shokupan.dev/plugins/graphql)** | Support for Apollo Server and GraphQL Yoga. |
82
+ | **[HTTP Server](https://shokupan.dev/plugins/http-server)** | Use standard Node.js HTTP/HTTPS servers. |
83
+ | **[MCP Server](https://shokupan.dev/plugins/mcp-server)** | Expose your API as tools to LLMs. |
84
+ | **[Socket.IO](https://shokupan.dev/plugins/socket-io)** | Easy integration with Socket.IO. |
85
+ | **[Proxy](https://shokupan.dev/plugins/proxy)** | Create reverse proxies. |
86
+ | **[OpenAPI Validator](https://shokupan.dev/plugins/openapi-validation)** | Validate requests against OpenAPI specs. |
87
+ | **[Idempotency](https://shokupan.dev/plugins/idempotency)** | Ensure safe retries for non-idempotent operations. |
1001
88
 
1002
89
  ## 🚀 Advanced Features
1003
90
 
1004
- ### Dependency Injection
1005
-
1006
- Shokupan includes a simple but powerful DI container:
1007
-
1008
- ```typescript
1009
- import { Container } from 'shokupan';
1010
-
1011
- // Register services
1012
- class Database {
1013
- query(sql: string) {
1014
- return [];
1015
- }
1016
- }
1017
-
1018
- class UserService {
1019
- constructor(private db: Database) {}
1020
-
1021
- getUsers() {
1022
- return this.db.query('SELECT * FROM users');
1023
- }
1024
- }
1025
-
1026
- Container.register('db', Database);
1027
- Container.register('userService', UserService);
1028
-
1029
- // Use in controllers
1030
- @Controller('/users')
1031
- export class UserController {
1032
- constructor(
1033
- private userService: UserService = Container.resolve('userService')
1034
- ) {}
1035
-
1036
- @Get('/')
1037
- getUsers() {
1038
- return this.userService.getUsers();
1039
- }
1040
- }
1041
- ```
1042
-
1043
- ### OpenAPI Generation
1044
-
1045
- Generate OpenAPI specs automatically and add custom documentation:
1046
-
1047
- ```typescript
1048
- // Add OpenAPI metadata to routes
1049
- app.get('/users/:id', {
1050
- summary: 'Get user by ID',
1051
- description: 'Retrieves a single user by their unique identifier',
1052
- tags: ['Users'],
1053
- parameters: [{
1054
- name: 'id',
1055
- in: 'path',
1056
- required: true,
1057
- schema: { type: 'string' }
1058
- }],
1059
- responses: {
1060
- 200: {
1061
- description: 'User found',
1062
- content: {
1063
- 'application/json': {
1064
- schema: {
1065
- type: 'object',
1066
- properties: {
1067
- id: { type: 'string' },
1068
- name: { type: 'string' },
1069
- email: { type: 'string' }
1070
- }
1071
- }
1072
- }
1073
- }
1074
- },
1075
- 404: {
1076
- description: 'User not found'
1077
- }
1078
- }
1079
- }, (ctx) => {
1080
- return { id: ctx.params.id, name: 'Alice' };
1081
- });
1082
-
1083
- // Generate OpenAPI spec
1084
- const spec = app.computeOpenAPISpec({
1085
- info: {
1086
- title: 'My API',
1087
- version: '1.0.0'
1088
- }
1089
- });
1090
- ```
1091
-
1092
- ### Sub-Requests
1093
-
1094
- Make internal requests without HTTP overhead:
1095
-
1096
- ```typescript
1097
- import { ShokupanRouter } from 'shokupan';
1098
-
1099
- const router = new ShokupanRouter();
1100
-
1101
- // Service endpoints
1102
- router.get('/wines/red', async (ctx) => {
1103
- const response = await fetch('https://api.sampleapis.com/wines/reds');
1104
- return response.json();
1105
- });
1106
-
1107
- router.get('/wines/white', async (ctx) => {
1108
- const response = await fetch('https://api.sampleapis.com/wines/whites');
1109
- return response.json();
1110
- });
1111
-
1112
- // Aggregate endpoint using sub-requests
1113
- router.get('/wines/all', async (ctx) => {
1114
- // Make parallel sub-requests
1115
- const [redResponse, whiteResponse] = await Promise.all([
1116
- router.internalRequest('/wines/red'),
1117
- router.internalRequest('/wines/white')
1118
- ]);
1119
-
1120
- const red = await redResponse.json();
1121
- const white = await whiteResponse.json();
1122
-
1123
- return { red, white };
1124
- });
1125
-
1126
- app.mount('/api', router);
1127
-
1128
- // GET /api/wines/all
1129
- // Returns both red and white wines aggregated
1130
- ```
1131
-
1132
- Sub-requests are great for:
1133
- - Service composition
1134
- - Backend-for-Frontend (BFF) patterns
1135
- - Internal API aggregation
1136
- - Testing
1137
-
1138
- ### OpenTelemetry
1139
-
1140
- Built-in distributed tracing support:
1141
-
1142
- ```typescript
1143
- const app = new Shokupan({
1144
- port: 3000,
1145
- development: true,
1146
- enableAsyncLocalStorage: true // Enable for better trace context
1147
- });
1148
-
1149
- // Tracing is automatic!
1150
- // All routes and middleware are instrumented
1151
- // Sub-requests maintain trace context
1152
- ```
1153
-
1154
- Configure OpenTelemetry exporters in your environment:
1155
-
1156
- ```typescript
1157
- // src/instrumentation.ts
1158
- import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
1159
- import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
1160
-
1161
- const provider = new NodeTracerProvider();
1162
- provider.addSpanProcessor(
1163
- new BatchSpanProcessor(
1164
- new OTLPTraceExporter({
1165
- url: 'http://localhost:4318/v1/traces'
1166
- })
1167
- )
1168
- );
1169
- provider.register();
1170
- ```
1171
-
1172
- ### Server Factory (Node.js & Deno)
1173
-
1174
- Run Shokupan on Node.js or Deno using the server adapter:
1175
-
1176
- ```typescript
1177
- import { Shokupan, createHttpServer } from 'shokupan';
1178
-
1179
- const app = new Shokupan({
1180
- // Use Node.js http module
1181
- serverFactory: createHttpServer()
1182
- });
1183
-
1184
- app.get('/', () => ({ message: 'Running on Node!' }));
1185
-
1186
- app.listen(3000);
1187
- ```
1188
-
1189
- ### Automatic Backpressure
1190
-
1191
- Protect your server from overload by shedding load when CPU usage is high:
1192
-
1193
- ```typescript
1194
- const app = new Shokupan({
1195
- // Monitor CPU and reject requests when usage > 80%
1196
- autoBackpressureFeedback: true,
1197
- autoBackpressureLevel: 80
1198
- });
1199
- ```
1200
-
1201
- When the threshold is reached, the server will return `429 Too Many Requests`.
1202
-
1203
- ## 📦 Migration Guides
1204
-
1205
-
1206
- ### From Express
1207
-
1208
- Shokupan is designed to feel familiar to Express developers. Here's how to migrate:
1209
-
1210
- #### Basic Server
1211
-
1212
- **Express:**
1213
- ```typescript
1214
- import express from 'express';
1215
-
1216
- const app = express();
1217
-
1218
- app.get('/', (req, res) => {
1219
- res.json({ message: 'Hello' });
1220
- });
1221
-
1222
- app.listen(3000);
1223
- ```
1224
-
1225
- **Shokupan:**
1226
- ```typescript
1227
- import { Shokupan } from 'shokupan';
1228
-
1229
- const app = new Shokupan({ port: 3000 });
1230
-
1231
- app.get('/', (ctx) => {
1232
- return { message: 'Hello' };
1233
- });
1234
-
1235
- app.listen();
1236
- ```
1237
-
1238
- #### Request/Response
1239
-
1240
- **Express:**
1241
- ```typescript
1242
- app.get('/users/:id', (req, res) => {
1243
- const id = req.params.id;
1244
- const page = req.query.page;
1245
- const token = req.headers.authorization;
1246
-
1247
- res.status(200).json({
1248
- id,
1249
- page,
1250
- authenticated: !!token
1251
- });
1252
- });
1253
- ```
1254
-
1255
- **Shokupan:**
1256
- ```typescript
1257
- app.get('/users/:id', (ctx) => {
1258
- const id = ctx.params.id;
1259
- const page = ctx.query.get('page');
1260
- const token = ctx.headers.get('authorization');
1261
-
1262
- return ctx.json({
1263
- id,
1264
- page,
1265
- authenticated: !!token
1266
- }, 200);
1267
-
1268
- // Or simply return an object (auto JSON, status 200)
1269
- return { id, page, authenticated: !!token };
1270
- });
1271
- ```
1272
-
1273
- #### Middleware
1274
-
1275
- **Express:**
1276
- ```typescript
1277
- app.use((req, res, next) => {
1278
- console.log(`${req.method} ${req.path}`);
1279
- next();
1280
- });
1281
-
1282
- app.use(express.json());
1283
- app.use(cors());
1284
- ```
1285
-
1286
- **Shokupan:**
1287
- ```typescript
1288
- import { Cors } from 'shokupan';
1289
-
1290
- app.use(async (ctx, next) => {
1291
- console.log(`${ctx.method} ${ctx.path}`);
1292
- return next();
1293
- });
1294
-
1295
- // Body parsing is built-in, no middleware needed
1296
- app.use(Cors());
1297
- ```
1298
-
1299
- #### Static Files
1300
-
1301
- **Express:**
1302
- ```typescript
1303
- app.use('/public', express.static('public'));
1304
- ```
1305
-
1306
- **Shokupan:**
1307
- ```typescript
1308
- app.static('/public', {
1309
- root: './public',
1310
- listDirectory: true
1311
- });
1312
- ```
1313
-
1314
- #### Key Differences
1315
-
1316
- 1. **Context vs Req/Res**: Shokupan uses a single `ctx` object
1317
- 2. **Return vs Send**: Return values directly instead of calling `res.json()` or `res.send()`
1318
- 3. **Built-in Parsing**: Body parsing is automatic, no need for `express.json()`
1319
- 4. **Async by Default**: All handlers and middleware are naturally async
1320
- 5. **Web Standard APIs**: Uses `Headers`, `URL`, `Response` etc. from web standards
1321
-
1322
- ### From Koa
1323
-
1324
- Shokupan's context-based approach is heavily inspired by Koa:
1325
-
1326
- #### Basic Differences
1327
-
1328
- **Koa:**
1329
- ```typescript
1330
- import Koa from 'koa';
1331
-
1332
- const app = new Koa();
1333
-
1334
- app.use(async (ctx, next) => {
1335
- ctx.body = { message: 'Hello' };
1336
- });
1337
-
1338
- app.listen(3000);
1339
- ```
1340
-
1341
- **Shokupan:**
1342
- ```typescript
1343
- import { Shokupan } from 'shokupan';
1344
-
1345
- const app = new Shokupan({ port: 3000 });
1346
-
1347
- app.get('/', async (ctx) => {
1348
- return { message: 'Hello' };
1349
- });
1350
-
1351
- app.listen();
1352
- ```
1353
-
1354
- #### Middleware
1355
-
1356
- **Koa:**
1357
- ```typescript
1358
- app.use(async (ctx, next) => {
1359
- const start = Date.now();
1360
- await next();
1361
- const ms = Date.now() - start;
1362
- console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
1363
- });
1364
- ```
1365
-
1366
- **Shokupan:**
1367
- ```typescript
1368
- app.use(async (ctx, next) => {
1369
- const start = Date.now();
1370
- const result = await next();
1371
- const ms = Date.now() - start;
1372
- console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
1373
- return result; // Don't forget to return!
1374
- });
1375
- ```
1376
-
1377
- #### Routing
1378
-
1379
- **Koa (with koa-router):**
1380
- ```typescript
1381
- import Router from '@koa/router';
1382
-
1383
- const router = new Router();
1384
-
1385
- router.get('/users/:id', (ctx) => {
1386
- ctx.body = { id: ctx.params.id };
1387
- });
1388
-
1389
- app.use(router.routes());
1390
- ```
1391
-
1392
- **Shokupan:**
1393
- ```typescript
1394
- import { ShokupanRouter } from 'shokupan';
1395
-
1396
- const router = new ShokupanRouter();
1397
-
1398
- router.get('/users/:id', (ctx) => {
1399
- return { id: ctx.params.id };
1400
- });
1401
-
1402
- app.mount('/', router);
1403
- ```
1404
-
1405
- #### Key Differences
1406
-
1407
- 1. **Return Value**: Shokupan requires returning the response from middleware
1408
- 2. **Routing**: Built-in routing, no need for external router package
1409
- 3. **Context Properties**: Some property names differ (`ctx.path` vs `ctx.url`)
1410
- 4. **Body Parsing**: Built-in, no need for koa-bodyparser
1411
-
1412
- ### From NestJS
1413
-
1414
- Moving from NestJS to Shokupan:
1415
-
1416
- #### Controllers
1417
-
1418
- **NestJS:**
1419
- ```typescript
1420
- import { Controller, Get, Post, Param, Body } from '@nestjs/common';
1421
-
1422
- @Controller('users')
1423
- export class UserController {
1424
- @Get(':id')
1425
- getUser(@Param('id') id: string) {
1426
- return { id, name: 'Alice' };
1427
- }
1428
-
1429
- @Post()
1430
- createUser(@Body() body: CreateUserDto) {
1431
- return { created: body };
1432
- }
1433
- }
1434
- ```
1435
-
1436
- **Shokupan:**
1437
- ```typescript
1438
- import { Controller, Get, Post, Param, Body } from 'shokupan';
1439
-
1440
- @Controller('/users')
1441
- export class UserController {
1442
- @Get('/:id')
1443
- getUser(@Param('id') id: string) {
1444
- return { id, name: 'Alice' };
1445
- }
1446
-
1447
- @Post('/')
1448
- createUser(@Body() body: CreateUserDto) {
1449
- return { created: body };
1450
- }
1451
- }
1452
- ```
1453
-
1454
- #### Dependency Injection
1455
-
1456
- **NestJS:**
1457
- ```typescript
1458
- import { Injectable } from '@nestjs/common';
1459
-
1460
- @Injectable()
1461
- export class UserService {
1462
- getUsers() {
1463
- return [];
1464
- }
1465
- }
1466
-
1467
- @Controller('users')
1468
- export class UserController {
1469
- constructor(private userService: UserService) {}
1470
-
1471
- @Get()
1472
- getUsers() {
1473
- return this.userService.getUsers();
1474
- }
1475
- }
1476
- ```
1477
-
1478
- **Shokupan:**
1479
- ```typescript
1480
- import { Container } from 'shokupan';
1481
-
1482
- class UserService {
1483
- getUsers() {
1484
- return [];
1485
- }
1486
- }
1487
-
1488
- Container.register('userService', UserService);
1489
-
1490
- @Controller('/users')
1491
- export class UserController {
1492
- constructor(
1493
- private userService: UserService = Container.resolve('userService')
1494
- ) {}
1495
-
1496
- @Get('/')
1497
- getUsers() {
1498
- return this.userService.getUsers();
1499
- }
1500
- }
1501
- ```
1502
-
1503
- #### Guards
1504
-
1505
- **NestJS:**
1506
- ```typescript
1507
- import { CanActivate, ExecutionContext } from '@nestjs/common';
1508
-
1509
- export class AuthGuard implements CanActivate {
1510
- canActivate(context: ExecutionContext): boolean {
1511
- const request = context.switchToHttp().getRequest();
1512
- return validateToken(request.headers.authorization);
1513
- }
1514
- }
1515
-
1516
- @Controller('admin')
1517
- @UseGuards(AuthGuard)
1518
- export class AdminController {}
1519
- ```
1520
-
1521
- **Shokupan:**
1522
- ```typescript
1523
- import { Middleware, Use } from 'shokupan';
1524
-
1525
- const authGuard: Middleware = async (ctx, next) => {
1526
- if (!validateToken(ctx.headers.get('authorization'))) {
1527
- return ctx.json({ error: 'Unauthorized' }, 401);
1528
- }
1529
- return next();
1530
- };
1531
-
1532
- @Controller('/admin')
1533
- @Use(authGuard)
1534
- export class AdminController {}
1535
- ```
1536
-
1537
- #### Validation
1538
-
1539
- **NestJS:**
1540
- ```typescript
1541
- import { IsString, IsEmail, IsNumber } from 'class-validator';
1542
-
1543
- export class CreateUserDto {
1544
- @IsString()
1545
- name: string;
1546
-
1547
- @IsEmail()
1548
- email: string;
1549
-
1550
- @IsNumber()
1551
- age: number;
1552
- }
1553
- ```
1554
-
1555
- **Shokupan:**
1556
- ```typescript
1557
- import { z } from 'zod';
1558
- import { validate } from 'shokupan';
1559
-
1560
- const createUserSchema = z.object({
1561
- name: z.string(),
1562
- email: z.string().email(),
1563
- age: z.number()
1564
- });
1565
-
1566
- @Post('/')
1567
- @Use(validate({ body: createUserSchema }))
1568
- createUser(@Body() body: any) {
1569
- return { created: body };
1570
- }
1571
- ```
1572
-
1573
- #### Key Differences
1574
-
1575
- 1. **Lighter DI**: Manual registration vs automatic
1576
- 2. **Middleware over Guards**: Use middleware pattern instead of guards
1577
- 3. **Validation Libraries**: Use Zod/Ajv/TypeBox instead of class-validator
1578
- 4. **Module System**: No modules, simpler structure
1579
- 5. **Less Boilerplate**: More straightforward setup
1580
-
1581
- ### Using Express Middleware
1582
-
1583
- Many Express middleware packages work with Shokupan:
1584
-
1585
- ```typescript
1586
- import { Shokupan, useExpress } from 'shokupan';
1587
- import helmet from 'helmet';
1588
- import compression from 'compression';
1589
-
1590
- const app = new Shokupan();
1591
-
1592
- // Use Express middleware
1593
- app.use(useExpress(helmet()));
1594
- app.use(useExpress(compression()));
1595
- ```
1596
-
1597
- **Note**: While many Express middleware will work, native Shokupan plugins are recommended for better performance and TypeScript support.
1598
-
1599
- ## 🧪 Testing
1600
-
1601
- Shokupan applications are easy to test using Bun's built-in test runner.
1602
- It can directly pass requests to the application or a router without requiring the
1603
- server to fully start, bind to a port or listen for connections. This makes mocking
1604
- the server unnecessary and allows for faster and more reliable testing. Additionally
1605
- you can directly test authenticated endpoints without the need for a session or cookie.
1606
-
1607
- ```typescript
1608
- import { describe, it, expect } from 'bun:test';
1609
- import { Shokupan } from 'shokupan';
1610
-
1611
- describe('My App', () => {
1612
- it('should return hello world', async () => {
1613
- const app = new Shokupan();
1614
-
1615
- app.get('/', () => ({ message: 'Hello' }));
1616
-
1617
- // Process a request without starting the server
1618
- const res = await app.testRequest({
1619
- method: 'GET',
1620
- path: '/'
1621
- });
1622
-
1623
- expect(res.status).toBe(200);
1624
- expect(res.data).toEqual({ message: 'Hello' });
1625
- });
1626
- });
1627
- ```
1628
-
1629
- ## 🚢 Deployment
1630
-
1631
- Since Shokupan is built on Bun, deployment is straightforward.
1632
-
1633
- ### Using Bun
1634
-
1635
- ```bash
1636
- bun run src/main.ts
1637
- ```
1638
-
1639
- ### Docker
1640
-
1641
- ```dockerfile
1642
- FROM oven/bun:1-alpine
1643
-
1644
- WORKDIR /app
1645
-
1646
- COPY . .
1647
- RUN bun install --production
1648
-
1649
- EXPOSE 3000
1650
-
1651
- CMD ["bun", "run", "src/main.ts"]
1652
- ```
1653
-
1654
- ## 🛠️ CLI Tools
1655
-
1656
- Shokupan includes a CLI for scaffolding:
1657
-
1658
- ```bash
1659
- # Install globally
1660
- bun add -g shokupan
1661
-
1662
- # Or use with bunx
1663
- bunx shokupan
1664
- ```
1665
-
1666
- ### Generate Controller
1667
-
1668
- ```bash
1669
- shokupan generate controller User
1670
- # or
1671
- skp g controller User
1672
- ```
1673
-
1674
- Generates:
1675
- ```typescript
1676
- import { Controller, Get, Post, Put, Delete, Param, Body } from 'shokupan';
1677
-
1678
- @Controller('/user')
1679
- export class UserController {
1680
-
1681
- @Get('/')
1682
- async getAll() {
1683
- return { users: [] };
1684
- }
1685
-
1686
- @Get('/:id')
1687
- async getById(@Param('id') id: string) {
1688
- return { id };
1689
- }
1690
-
1691
- @Post('/')
1692
- async create(@Body() body: any) {
1693
- return { created: body };
1694
- }
1695
-
1696
- @Put('/:id')
1697
- async update(@Param('id') id: string, @Body() body: any) {
1698
- return { id, updated: body };
1699
- }
1700
-
1701
- @Delete('/:id')
1702
- async delete(@Param('id') id: string) {
1703
- return { id, deleted: true };
1704
- }
1705
- }
1706
- ```
1707
-
1708
- ### Generate Middleware
1709
-
1710
- ```bash
1711
- shokupan generate middleware auth
1712
- # or
1713
- skp g middleware auth
1714
- ```
1715
-
1716
- ### Generate Plugin
1717
-
1718
- ```bash
1719
- shokupan generate plugin custom
1720
- # or
1721
- skp g plugin custom
1722
- ```
1723
-
1724
- ## 📚 API Reference
1725
-
1726
- ### Shokupan Class
1727
-
1728
- Main application class.
1729
-
1730
- ```typescript
1731
- const app = new Shokupan(config?: ShokupanConfig);
1732
- ```
1733
-
1734
- **Config Options:**
1735
- - `port?: number` - Port to listen on (default: 3000)
1736
- - `hostname?: string` - Hostname (default: "localhost")
1737
- - `development?: boolean` - Development mode (default: auto-detect)
1738
- - `enableAsyncLocalStorage?: boolean` - Enable async context tracking
1739
- - `enableTracing?: boolean` - Enable OpenTelemetry tracing
1740
- - `enableOpenApiGen?: boolean` - Enable OpenAPI spec generation (default: true)
1741
- - `controllersOnly?: boolean` - If true, only allows controllers, disabling app.get/post/etc (default: false)
1742
- - `requestTimeout?: number` - Global request timeout (ms)
1743
- - `readTimeout?: number` - Request body read timeout (ms)
1744
- - `serverFactory?: ServerFactory` - Custom server factory (for Node.js/Deno support)
1745
- - `autoBackpressureFeedback?: boolean` - Enable automatic load shedding based on CPU usage
1746
- - `autoBackpressureLevel?: number` - CPU usage % threshold for backpressure (default: 60)
1747
- - `logger?: Logger` - Custom logger instance
1748
-
1749
- **Methods:**
1750
- - `add({ method, path, spec, handler, regex, group)` - Add a route with any HTTP method.
1751
- - `get(path, spec?, ...handlers)` - Add GET route
1752
- - `post(path, spec?, ...handlers)` - Add POST route
1753
- - `put(path, spec?, ...handlers)` - Add PUT route
1754
- - `patch(path, spec?, ...handlers)` - Add PATCH route
1755
- - `delete(path, spec?, ...handlers)` - Add DELETE route
1756
- - `options(path, spec?, ...handlers)` - Add OPTIONS route
1757
- - `head(path, spec?, ...handlers)` - Add HEAD route
1758
- - `use(middleware)` - Add middleware
1759
- - `mount(path, controller)` - Mount controller or router
1760
- - `static(path, options)` - Serve static files
1761
- - `listen(port?)` - Start server
1762
- - `testRequest(options)` - Process request (for testing purposes)
1763
- - `internalRequest(options)` - Make sub-request
1764
- - `computeOpenAPISpec(base)` - Generate OpenAPI spec
1765
-
1766
- ### ShokupanRouter Class
1767
-
1768
- Router for grouping operations, applying middleware, and mounting controllers. Additionally
1769
- they are effective for creating sub-applications that are independently tested. Routers can
1770
- have OpenAPI spec applied to all endpoints of the router. Additionally they can be mounted
1771
- onto the main application or other routers.
1772
-
1773
- When a router is mounted to an app, if you are using the DebugView plugin you will be able to
1774
- see it under the Registry tab and the Graph tab.
1775
-
1776
- ```typescript
1777
- const router = new ShokupanRouter(config?: ShokupanRouteConfig);
1778
- ```
1779
-
1780
- **Config Options:**
1781
- - `name?: string` - Name of the router
1782
- - `group?: string` - Group of the router
1783
- - `openapi?: boolean` - OpenAPI spec applied to all endpoints of the router
1784
-
1785
- **Methods:**
1786
- - `add({ method, path, spec, handler, regex, group)` - Add a route with any HTTP method.
1787
- - `get(path, spec?, ...handlers)` - Add GET route
1788
- - `post(path, spec?, ...handlers)` - Add POST route
1789
- - `put(path, spec?, ...handlers)` - Add PUT route
1790
- - `patch(path, spec?, ...handlers)` - Add PATCH route
1791
- - `delete(path, spec?, ...handlers)` - Add DELETE route
1792
- - `options(path, spec?, ...handlers)` - Add OPTIONS route
1793
- - `head(path, spec?, ...handlers)` - Add HEAD route
1794
- - `mount(path, controller)` - Mount controller or router
1795
- - `static(path, options)` - Serve static files
1796
- - `testRequest(options)` - Process request (for testing purposes)
1797
- - `internalRequest(options)` - Make sub-request
1798
-
1799
-
1800
- ### ShokupanContext
1801
-
1802
- Request context object.
1803
-
1804
- **Properties:**
1805
- - `req: Request` - Request object
1806
- - `method: string` - HTTP method
1807
- - `path: string` - URL path
1808
- - `url: URL` - Full URL
1809
- - `params: Record<string, string>` - Path parameters
1810
- - `query: URLSearchParams` - Query parameters
1811
- - `headers: Headers` - Request headers
1812
- - `state: Record<string, any>` - Shared state object
1813
- - `session: any` - Session data (with session plugin)
1814
- - `response: ShokupanResponse` - Response builder
1815
- - `ip: string` - Client IP address
1816
- - `hostname: string` - Hostname (e.g. "localhost")
1817
- - `host: string` - Host (e.g. "localhost:3000")
1818
- - `protocol: string` - Protocol (http/https)
1819
- - `secure: boolean` - Whether the request is secure over HTTPS
1820
- - `origin: string` - Origin URL
1821
- - `signal: AbortSignal` - Request abort signal (for standard fetch requests)
1822
-
1823
- **Methods:**
1824
- - `set(name: string, value: string): ShokupanContext` - Set a response header
1825
- - `setCookie(name: string, value: string, options?: CookieOptions): ShokupanContext` - Set a response cookie
1826
- - `send(body?: BodyInit, options?: ResponseInit): Response` - Return response
1827
- - `status(code: number): Response` - Return status code default response
1828
- - `body(): Promise<any>` - Parse request body
1829
- - `json(data: any, status?: number): ShokupanContext` - Return JSON response
1830
- - `text(data: string, status?: number): ShokupanContext` - Return text response
1831
- - `html(data: string, status?: number): ShokupanContext` - Return HTML response
1832
- - `jsx(element: any): ShokupanContext` - Render a JSX element
1833
- - `redirect(url: string, status?: number): ShokupanContext` - Redirect response
1834
- - `file(path: string, fileOptions?: BlobPropertyBag, responseOptions?: ResponseInit): Response` - Return file response
1835
-
1836
- ### Container
1837
-
1838
- Dependency injection container. This feature is still experimental and subject to change.
1839
-
1840
- ```typescript
1841
- Container.register(name: string, classOrFactory: any);
1842
- Container.resolve<T>(name: string): T;
1843
- Container.clear();
1844
- ```
1845
-
1846
- ## 🗺️ Roadmap
1847
-
1848
- ### Current Features
1849
-
1850
- - ✅ **Built for Bun** - Native performance
1851
- - ✅ **Express Ecosystem** - Middleware support
1852
- - ✅ **TypeScript First** - Decorators, Generics, Type Safety
1853
- - ✅ **Auto OpenAPI** - [Scalar](https://github.com/scalar/scalar) documentation
1854
- - ✅ **Rich Plugin System** - CORS, Session, Validation, Rate Limiting etc.
1855
- - ✅ **Dependency Injection** - Container for dependency injection
1856
- - ✅ **OpenTelemetry** - Built-in [OpenTelemetry](https://opentelemetry.io/) traces
1857
- - ✅ **OAuth2** - Built-in [OAuth2](https://oauth.net/2/) support
1858
- - ✅ **Request-Scoped Globals** - Request-scoped values via [AsyncLocalStorage](https://docs.deno.com/api/node/async_hooks/~/AsyncLocalStorage)
1859
- - ✅ **Runtime Compatibility** - Support for [Deno](https://deno.com/) and [Node.js](https://nodejs.org/)
1860
- - ✅ **Deep Introspection** - Type analysis for enhanced OpenAPI generation
1861
- - ✅ **Controller Mode** - Option for controller-only mode
1862
- - ✅ **Supports Node/Deno** - Shokupan can run on Node.js or Deno
1863
- - ✅ **OpenAPI Validation** - Built-in [OpenAPI](https://www.openapis.org/) validation
91
+ - **[Dependency Injection](https://shokupan.dev/guides/advanced)**: Built-in container for managing dependencies.
92
+ - **[OpenAPI Generation](https://shokupan.dev/core/controllers)**: Auto-generate specs from your code.
93
+ - **[Sub-Requests](https://shokupan.dev/core/routing)**: Make internal requests without HTTP overhead.
94
+ - **[OpenTelemetry](https://shokupan.dev/guides/production)**: Built-in distributed tracing.
95
+ - **[Type Augmentation](https://shokupan.dev/guides/global-type-augmentation)**: Extend global types for type-safety.
1864
96
 
1865
- ### Future Features
97
+ ## 📚 Guides & Reference
1866
98
 
1867
- - 🚧 **Framework Plugins** - Drop-in adapters for [Express](https://expressjs.com/), [Koa](https://koajs.com/), and [Elysia](https://elysiajs.com/)
1868
- - 🚧 **Enhanced WebSockets** - Event support and HTTP simulation
1869
- - 🚧 **Benchmarks** - Comprehensive performance comparisons
1870
- - 🚧 **RPC Support** - [tRPC](https://trpc.io/) and [gRPC](https://grpc.io/) integration
1871
- - 🚧 **Binary Formats** - [Protobuf](https://protobuf.dev/) and [MessagePack](https://msgpack.org/) support
1872
- - 🚧 **Reliability** - Circuit breaker pattern for resilience
1873
- - 🚧 **Standardized Errors** - Consistent 4xx/5xx error formats
99
+ - **[Migration Guides](https://shokupan.dev/migration)**: Detailed guides for migrating from Express, Koa, or NestJS.
100
+ - **[Testing](https://shokupan.dev/guides/testing)**: How to test your Shokupan application.
101
+ - **[Deployment](https://shokupan.dev/guides/deployment)**: Deploying to Bun, Docker, and more.
102
+ - **[CLI Reference](https://shokupan.dev/guides/cli)**: Documentation for the Shokupan CLI.
103
+ - **[API Reference](https://shokupan.dev/api)**: Complete API documentation.
104
+ - **[Roadmap](https://shokupan.dev/reference/roadmap)**: Future plans and features.
1874
105
 
1875
106
  ## 🤝 Contributing
1876
107