Skip to content

Commit ef024ad

Browse files
committed
feat: swagger config, examples and docs
1 parent 09e6c2b commit ef024ad

40 files changed

Lines changed: 966 additions & 158 deletions

README.md

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
</p>
1010

1111
<p align="center">
12-
<a href="./docs/index.md">Documentation</a> |
13-
<a href="./docs/getting-started.md">Getting started</a> |
14-
<a href="./docs/guides/index.md">Guides</a> |
15-
<a href="./docs/reference/index.md">Reference</a> |
12+
<a href="./docs/index.md">Documentation</a>
13+
<a href="./docs/getting-started.md">Getting started</a>
14+
<a href="./docs/guides/index.md">Guides</a>
15+
<a href="./docs/reference/index.md">Reference</a>
1616
<a href="./docs/faq.md">FAQ</a>
1717
</p>
1818

@@ -182,7 +182,7 @@ import { UserController } from "./controller.js";
182182
const app = createApp({
183183
schema: classValidator(),
184184
controllers: [UserController],
185-
swagger: true,
185+
docs: { swagger: true },
186186
openapi: {
187187
filePath: "./openapi.yaml",
188188
service: {
@@ -550,16 +550,18 @@ const app = createApp({
550550

551551
## Swagger UI
552552

553-
Enable the built-in Swagger UI by setting `swagger: true` or passing a config
553+
Enable the built-in Swagger UI by setting `docs: { swagger: true }` or passing a config
554554
object:
555555

556556
```ts
557557
const app = createApp({
558558
schema: classValidator(),
559559
controllers: [...],
560-
swagger: true,
560+
docs: { swagger: true },
561561
// or with a custom path:
562-
// swagger: { path: "/swagger" },
562+
// docs: { swagger: { path: "/swagger" } },
563+
// or with SwaggerUIBundle options:
564+
// docs: { swagger: { deepLinking: true, docExpansion: "list" } },
563565
});
564566
```
565567

@@ -575,18 +577,20 @@ bun add swagger-ui-dist
575577

576578
## Module Viewer
577579

578-
Enable the built-in module hierarchy viewer by setting `moduleViewer: true` or
580+
Enable the built-in module hierarchy viewer by setting `docs: { modules: true }` or
579581
passing a config object:
580582

581583
```ts
582584
const app = createApp({
583585
schema: classValidator(),
584586
imports: [AppModule],
585-
moduleViewer: true,
587+
docs: { modules: true },
586588
// or with a custom path:
587-
// moduleViewer: { path: "/modules" },
589+
// docs: { modules: { path: "/modules" } },
588590
// or with SVG configuration:
589-
// moduleViewer: { path: "/modules", svgPath: "/modules/graph.svg" },
591+
// docs: { modules: { path: "/modules", svgPath: "/modules/graph.svg" } },
592+
// enable both features at once:
593+
// docs: true,
590594
});
591595
```
592596

@@ -605,7 +609,7 @@ throws if a module exports a provider token it does not declare or re-export,
605609
it throws with the full module path when circular imports are found, and only
606610
providers listed in `exports` are visible outside the module that defines them.
607611

608-
When either Swagger UI or the module viewer is enabled, `/docs/` serves a small
612+
When either Swagger UI or the module viewer is enabled via `docs`, `/docs/` serves a small
609613
index page with links to the configured documentation endpoints.
610614

611615
The complex example uses this mode:

docs/concepts/index.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,25 @@ Concept pages explain how bun-openapi works before you move to implementation de
66

77
## Core Concepts
88

9-
- [Decorator Basics](decorators.md)
10-
- [Request Lifecycle](request-lifecycle.md)
9+
- [Decorator Basics](decorators.md) — how decorators drive routing, validation, and OpenAPI generation
10+
- [Request Lifecycle](request-lifecycle.md) — the full execution flow from HTTP request to response
11+
12+
## Architecture at a Glance
13+
14+
```
15+
createApp(config)
16+
|
17+
+-- Module resolution -> walk @Module import tree, build visibility maps
18+
+-- DI container setup -> register providers with scope and visibility
19+
+-- OpenAPI spec build -> read decorator metadata into OpenAPI 3.0.3
20+
+-- Route registration -> wire controllers to Bun route handlers
21+
`-- Docs endpoints -> docs.swagger + docs.modules (optional)
22+
```
1123

1224
## Practical Follow-Up
1325

1426
- [Getting Started](../getting-started.md)
1527
- [Controllers and Routes](../guides/controllers-and-routes.md)
1628
- [Request Binding](../guides/request-binding.md)
29+
- [Dependency Injection](../guides/dependency-injection.md)
30+
- [Modules](../guides/modules.md)

docs/concepts/request-lifecycle.md

Lines changed: 154 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,167 @@
22

33
[Docs Home](../index.md) | [Previous: Decorator Basics](decorators.md) | [Next: Controllers and Routes](../guides/controllers-and-routes.md)
44

5-
This is the practical execution flow inside createApp route handlers.
6-
7-
## Execution Order
8-
9-
1. Route match by path and HTTP method.
10-
2. Request data parsing and binding:
11-
- params
12-
- query
13-
- headers
14-
- body
15-
3. Schema validation and scalar coercion.
16-
4. Guard execution:
17-
- global guards
18-
- controller guards
19-
- method guards
20-
- security guards for @Security routes
21-
5. Interceptor chain:
22-
- global interceptors
23-
- controller interceptors
24-
- method interceptors
25-
6. Controller method execution.
26-
7. Optional response validation against @Returns.
27-
8. Final response formatting.
5+
This page describes the exact execution flow when an HTTP request hits a route handler built by `createApp()`.
6+
7+
## Full Request Flow
8+
9+
```
10+
Incoming HTTP Request
11+
|
12+
v
13+
+-------------------------------------------+
14+
| 1. Route matching |
15+
| Bun matches path and HTTP method |
16+
+-------------------------------------------+
17+
|
18+
v
19+
+-------------------------------------------+
20+
| 2. Request parsing and binding |
21+
| - path params |
22+
| - query string |
23+
| - headers |
24+
| - body |
25+
+-------------------------------------------+
26+
|
27+
v
28+
+-------------------------------------------+
29+
| 3. Schema validation and coercion |
30+
| - @Param(schema) |
31+
| - @Query(schema) |
32+
| - @Header(schema) |
33+
| - @Body(schema) |
34+
+-------------------------------------------+
35+
|
36+
+--> validation failure -> 400 Bad Request
37+
|
38+
v
39+
+-------------------------------------------+
40+
| 4. Middleware chain |
41+
| Global -> Controller -> Method |
42+
+-------------------------------------------+
43+
|
44+
v
45+
+-------------------------------------------+
46+
| 5. Guard execution |
47+
| Global -> Controller -> Method |
48+
| + security guards from @Security |
49+
+-------------------------------------------+
50+
|
51+
+--> false -> 403 Forbidden
52+
+--> Response -> short-circuit
53+
|
54+
v
55+
+-------------------------------------------+
56+
| 6. Interceptor chain |
57+
| Global -> Controller -> Method |
58+
| Each interceptor wraps next() |
59+
+-------------------------------------------+
60+
|
61+
v
62+
+-------------------------------------------+
63+
| 7. Controller method execution |
64+
| - create RequestScope |
65+
| - instantiate controller |
66+
| - resolve method arguments |
67+
| - call method(...args) |
68+
+-------------------------------------------+
69+
|
70+
v
71+
+-------------------------------------------+
72+
| 8. Optional response validation |
73+
| Validate final output against the |
74+
| @Returns schema for the status code |
75+
+-------------------------------------------+
76+
|
77+
+--> mismatch -> 500 Server Error
78+
|
79+
v
80+
+-------------------------------------------+
81+
| 9. Response formatting |
82+
| - status and headers |
83+
| - JSON or custom content type |
84+
| - @Render -> HTML via ViewEngine |
85+
+-------------------------------------------+
86+
|
87+
v
88+
HTTP Response
89+
```
90+
91+
## How the Layers Nest
92+
93+
Middleware, guards, and interceptors form concentric layers around your controller method. Understanding this nesting is key to writing correct cross-cutting logic:
94+
95+
```
96+
+-----------------------------------------------------+
97+
| Middleware |
98+
| (request, next) => Response |
99+
| |
100+
| +-----------------------------------------------+ |
101+
| | Guards | |
102+
| | canActivate(ctx) | |
103+
| | | |
104+
| | +-----------------------------------------+ | |
105+
| | | Interceptors | | |
106+
| | | intercept(ctx, next) | | |
107+
| | | | | |
108+
| | | +-----------------------------------+ | | |
109+
| | | | Controller method | | | |
110+
| | | +-----------------------------------+ | | |
111+
| | +-----------------------------------------+ | |
112+
| +-----------------------------------------------+ |
113+
+-----------------------------------------------------+
114+
```
115+
116+
| Layer | Interface | Purpose | Can Short-Circuit? |
117+
| ---------------- | -------------------------------------- | ------------------------------------ | ---------------------------------- |
118+
| **Middleware** | `(request, next) => Response` | Cross-cutting: logging, CORS, timing | Yes (skip `next()`) |
119+
| **Guards** | `canActivate(ctx) => bool \| Response` | Authorization gate | Yes (return `false` or `Response`) |
120+
| **Interceptors** | `intercept(ctx, next) => unknown` | Transform request/response, caching | Yes (skip `next()`) |
121+
122+
Within each layer, execution order is: **Global → Controller-level → Method-level**.
28123

29124
## Error Flow
30125

31-
Any error from validation, guards, interceptors, or controller methods is normalized into an HTTP response. You can customize this with errorFormatter in createApp.
126+
```
127+
Any exception
128+
|
129+
v
130+
+-------------------------------------------+
131+
| mapError(error) |
132+
+-------------------------------------------+
133+
|
134+
+--> HttpException -> status/body/headers
135+
+--> Error -> 500 + error.message
136+
+--> other -> 500 + Internal Server Error
137+
|
138+
v
139+
+-------------------------------------------+
140+
| errorFormatter(error, context) |
141+
| optional global formatter |
142+
+-------------------------------------------+
143+
|
144+
v
145+
HTTP response
146+
```
147+
148+
Any error from validation, guards, interceptors, or controller methods is caught and normalized into an HTTP response. Customize the shape with `errorFormatter` in `createApp`.
32149

33150
## Caller Context
34151

35-
bun-openapi stores endpoint metadata in async local storage:
152+
bun-openapi stores endpoint metadata in `AsyncLocalStorage` during handler execution:
153+
154+
- `className` — the controller class name
155+
- `methodName` — the handler method name
156+
157+
Use `getCallerContext()` in services or logging helpers to access this without manually passing method names:
36158

37-
- className
38-
- methodName
159+
```ts
160+
import { getCallerContext } from "bun-openapi";
39161

40-
Use getCallerContext() in logging and metrics helpers to label output without manually passing method names.
162+
function log(message: string) {
163+
const ctx = getCallerContext();
164+
console.log(`[${ctx?.className}.${ctx?.methodName}] ${message}`);
165+
}
166+
```
41167

42168
Example usage is available in [examples/08_logging/server.ts](https://github.com/xseman/bun-openapi/blob/master/examples/08_logging/server.ts).

docs/examples/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Runnable examples are under the repository `examples/` directory. They are numbe
2020
| [12_form-auth](https://github.com/xseman/bun-openapi/tree/master/examples/12_form-auth/) | Form-based web auth with JWT-in-cookie, DataSource DI, and Handlebars views |
2121
| [13_typeorm-relations](https://github.com/xseman/bun-openapi/tree/master/examples/13_typeorm-relations/) | TypeORM one-to-many / many-to-one relations with DataSource DI |
2222
| [14_session-auth](https://github.com/xseman/bun-openapi/tree/master/examples/14_session-auth/) | Stateful server-side sessions with HttpOnly cookie, DataSource DI, and Handlebars |
23+
| [15_request-scope](https://github.com/xseman/bun-openapi/tree/master/examples/15_request-scope/) | Request-scoped DI with `@Injectable({ scope: "request" })` for per-request state |
2324

2425
## How To Run An Example
2526

docs/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ import { classValidator } from "bun-openapi/adapters/class-validator";
103103
const app = createApp({
104104
schema: classValidator(),
105105
controllers: [UserController],
106-
swagger: true,
106+
docs: { swagger: true },
107107
openapi: {
108108
service: {
109109
name: "my-api",

0 commit comments

Comments
 (0)