@vnphu/nestjs-api-explorer 0.1.0 → 0.2.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.
package/README.md CHANGED
@@ -1,136 +1,468 @@
1
- # nestjs-api-docs
1
+ # @vnphu/nestjs-api-explorer
2
2
 
3
- An in-app API explorer for NestJS — like Postman, but embedded directly into your application. Zero external dependencies, dark-themed, and auto-disabled in production.
3
+ An in-app API explorer for NestJS — like Postman, but embedded directly into your application. Auto-discovers your routes from the Express router and lets you document them with a simple Markdown file.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **Route auto-discovery** — scans the live Express router, no decorators needed
8
+ - **Markdown docs** — document body fields, query params, headers, and responses in a simple `.md` file
8
9
  - **Full request builder** — path params, query params, headers, auth, and body
9
- - **Auth support** — Bearer Token, Basic Auth, API Key (header or query)
10
+ - **Global auth** — set Bearer Token / Basic Auth / API Key once, applies to all requests
10
11
  - **Body types** — JSON (with formatter), Form-encoded, Plain text
11
12
  - **Response viewer** — syntax-highlighted JSON, status badge, timing, response headers
12
- - **Resizable panels** — drag-to-resize sidebar and request/response split
13
- - **Auto-disabled in production** — safe to leave in your codebase
13
+ - **Route grouping** — groups defined in your docs file, with collapse/expand
14
+ - **Auto-disabled in production** — `NODE_ENV=production` disables the explorer automatically
14
15
 
15
16
  ## Installation
16
17
 
17
18
  ```bash
18
- npm install nestjs-api-docs
19
- # or
20
- yarn add nestjs-api-docs
21
- # or
22
- pnpm add nestjs-api-docs
19
+ npm install @vnphu/nestjs-api-explorer
23
20
  ```
24
21
 
25
- ## Usage
22
+ ## Quick Start
26
23
 
27
- Register the module in your `AppModule`:
24
+ ### 1. Import the module
28
25
 
29
26
  ```typescript
30
- import { Module } from '@nestjs/common';
31
- import { ApiExplorerModule } from 'nestjs-api-docs';
27
+ import { ApiExplorerModule } from '@vnphu/nestjs-api-explorer';
32
28
 
33
29
  @Module({
34
30
  imports: [
35
31
  ApiExplorerModule.register({
36
- path: 'api-explorer', // UI available at GET /api-explorer
32
+ path: 'api-explorer', // serves UI at /api-explorer
37
33
  title: 'My API',
38
- enabled: true, // auto-false in production
34
+ docsFile: './api-explorer.md', // optional but recommended
39
35
  }),
40
36
  ],
41
37
  })
42
38
  export class AppModule {}
43
39
  ```
44
40
 
45
- Then start your app and visit:
41
+ Open your browser at `http://localhost:3000/api-explorer`.
46
42
 
47
- ```
48
- http://localhost:3000/api-explorer
43
+ ---
44
+
45
+ ## Documenting your API (Markdown file)
46
+
47
+ Create a file `api-explorer.md` in your project root (or anywhere — just set `docsFile` to the path).
48
+
49
+ ### Full example
50
+
51
+ ```markdown
52
+ # Auth
53
+
54
+ ## POST /auth/login
55
+ > Đăng nhập và nhận JWT token
56
+
57
+ body:
58
+ email: string, required, isEmail
59
+ password: string, required, minLength(6)
60
+
61
+ response:
62
+ token: string
63
+ expiresIn: number
64
+
65
+ ## POST /auth/register
66
+ > Đăng ký tài khoản mới
67
+
68
+ body:
69
+ name: string, required, minLength(2), maxLength(50)
70
+ email: string, required, isEmail
71
+ password: string, required, minLength(8)
72
+ role: string, isIn(admin,user,guest)
73
+
74
+ ## GET /auth/me
75
+ > Lấy thông tin user hiện tại
76
+
77
+ headers:
78
+ Authorization: required, Bearer {token}
79
+
80
+ response:
81
+ id: string
82
+ email: string
83
+ name: string
84
+
85
+ ---
86
+
87
+ # Users
88
+
89
+ ## GET /users
90
+ > Danh sách users (admin only)
91
+
92
+ headers:
93
+ Authorization: required
94
+
95
+ query:
96
+ page: number, default(1)
97
+ limit: number, default(20), max(100)
98
+ search: string, tìm theo tên hoặc email
99
+ role: string, isIn(admin,user,guest)
100
+ sortBy: string, isIn(name,email,createdAt)
101
+ sortOrder: string, isIn(asc,desc)
102
+
103
+ ## GET /users/:id
104
+ > Lấy thông tin một user
105
+
106
+ params:
107
+ id: string, required, MongoDB ObjectId
108
+
109
+ headers:
110
+ Authorization: required
111
+
112
+ ## POST /users
113
+ > Tạo user mới
114
+
115
+ headers:
116
+ Authorization: required
117
+
118
+ body:
119
+ name: string, required, minLength(2)
120
+ email: string, required, isEmail
121
+ password: string, required, minLength(8)
122
+ role: string, required, isIn(admin,user,guest)
123
+ isActive: boolean
124
+
125
+ ## PUT /users/:id
126
+ > Cập nhật user
127
+
128
+ params:
129
+ id: string, required
130
+
131
+ headers:
132
+ Authorization: required
133
+
134
+ body:
135
+ name: string, minLength(2)
136
+ email: string, isEmail
137
+ role: string, isIn(admin,user,guest)
138
+ isActive: boolean
139
+
140
+ ## DELETE /users/:id
141
+ > Xoá user (không thể hoàn tác)
142
+
143
+ params:
144
+ id: string, required
145
+
146
+ headers:
147
+ Authorization: required
148
+
149
+ ---
150
+
151
+ # Products
152
+
153
+ ## GET /products
154
+ > Danh sách sản phẩm (public)
155
+
156
+ query:
157
+ page: number, default(1)
158
+ limit: number, default(20)
159
+ search: string
160
+ minPrice: number, min(0)
161
+ maxPrice: number
162
+ inStock: boolean
163
+
164
+ ## GET /products/:id
165
+ > Chi tiết sản phẩm
166
+
167
+ params:
168
+ id: string, required
169
+
170
+ ## GET /products/:id/reviews
171
+ > Danh sách đánh giá
172
+
173
+ params:
174
+ id: string, required
175
+
176
+ query:
177
+ page: number, default(1)
178
+ limit: number, default(10)
179
+
180
+ ## POST /products
181
+ > Tạo sản phẩm mới
182
+
183
+ headers:
184
+ Authorization: required
185
+
186
+ body:
187
+ name: string, required, minLength(2), maxLength(200)
188
+ price: number, required, min(0)
189
+ description: string, maxLength(2000)
190
+ category: string, required
191
+ stock: number, min(0)
192
+ isActive: boolean
193
+ tags: array
194
+ meta: object
195
+
196
+ ## PUT /products/:id
197
+ > Cập nhật sản phẩm
198
+
199
+ params:
200
+ id: string, required
201
+
202
+ headers:
203
+ Authorization: required
204
+
205
+ body:
206
+ name: string, minLength(2)
207
+ price: number, min(0)
208
+ description: string
209
+ stock: number, min(0)
210
+ isActive: boolean
211
+
212
+ ## DELETE /products/:id
213
+ > Xoá sản phẩm
214
+
215
+ params:
216
+ id: string, required
217
+
218
+ headers:
219
+ Authorization: required
220
+
221
+ ---
222
+
223
+ # Orders
224
+
225
+ ## GET /orders
226
+ > Danh sách đơn hàng
227
+
228
+ headers:
229
+ Authorization: required
230
+
231
+ query:
232
+ page: number, default(1)
233
+ limit: number, default(20)
234
+ status: string, isIn(pending,processing,shipped,delivered,cancelled)
235
+ from: string, ISO date string
236
+ to: string, ISO date string
237
+
238
+ ## POST /orders
239
+ > Đặt hàng
240
+
241
+ headers:
242
+ Authorization: required
243
+
244
+ body:
245
+ productId: string, required
246
+ quantity: number, required, min(1)
247
+ address: string, required
248
+ note: string, maxLength(500)
249
+ couponCode: string
250
+
251
+ response:
252
+ orderId: string
253
+ status: string
254
+ total: number
255
+
256
+ ## GET /orders/:orderId
257
+ > Chi tiết đơn hàng
258
+
259
+ params:
260
+ orderId: string, required
261
+
262
+ headers:
263
+ Authorization: required
264
+
265
+ ## PUT /orders/:orderId/status
266
+ > Cập nhật trạng thái (admin)
267
+
268
+ params:
269
+ orderId: string, required
270
+
271
+ headers:
272
+ Authorization: required
273
+
274
+ body:
275
+ status: string, required, isIn(processing,shipped,delivered,cancelled)
276
+ note: string
277
+
278
+ ## DELETE /orders/:orderId
279
+ > Huỷ đơn hàng
280
+
281
+ params:
282
+ orderId: string, required
283
+
284
+ headers:
285
+ Authorization: required
49
286
  ```
50
287
 
51
- ## Options
288
+ ---
52
289
 
53
- | Option | Type | Default | Description |
54
- |-----------|-----------|------------------|-----------------------------------------------------------------|
55
- | `path` | `string` | `'api-explorer'` | URL path prefix where the explorer is served |
56
- | `title` | `string` | `'API Explorer'` | Title shown in the browser tab and UI header |
57
- | `enabled` | `boolean` | `true` | Explicitly enable/disable. Always disabled when `NODE_ENV=production` |
290
+ ## Markdown syntax reference
58
291
 
59
- ## Endpoints
292
+ ### Structure
60
293
 
61
- Once registered, two endpoints are added:
294
+ | Syntax | Meaning |
295
+ |--------|---------|
296
+ | `# Group Name` | Route group — shown as a collapsible section in the sidebar |
297
+ | `## METHOD /path` | Route definition. METHOD = GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD |
298
+ | `> Description text` | Route description. Multiple `>` lines are joined with newline |
299
+ | `body:` | Start of body fields section |
300
+ | `query:` | Start of query params section |
301
+ | `params:` | Start of path params section |
302
+ | `headers:` | Start of required headers section |
303
+ | `response:` | Start of response fields section |
304
+ | `---` | Visual separator (ignored by parser) |
62
305
 
63
- | Method | Path | Description |
64
- |--------|------------------------|--------------------------------------|
65
- | `GET` | `/{path}` | Serves the explorer HTML UI |
66
- | `GET` | `/{path}/routes` | Returns discovered routes as JSON |
306
+ ### Field syntax
67
307
 
68
- ### Route response format
308
+ Each field must be **indented** (at least one space or tab) under its section header:
69
309
 
70
- ```json
71
- [
72
- {
73
- "method": "GET",
74
- "path": "/users",
75
- "params": []
76
- },
77
- {
78
- "method": "GET",
79
- "path": "/users/:id",
80
- "params": ["id"]
81
- }
82
- ]
310
+ ```
311
+ fieldName: type, rule1, rule2, description text
312
+ ```
313
+
314
+ **Type** (first token after `:`) — optional, one of:
315
+
316
+ | Type | Meaning |
317
+ |------|---------|
318
+ | `string` | Text value |
319
+ | `number` | Numeric value |
320
+ | `boolean` | `true` or `false` |
321
+ | `array` | JSON array |
322
+ | `object` | JSON object |
323
+
324
+ **Built-in rules** (recognized and highlighted):
325
+
326
+ | Rule | Example |
327
+ |------|---------|
328
+ | `required` | Field is required |
329
+ | `isEmail` | Must be a valid email |
330
+ | `isUrl` | Must be a valid URL |
331
+ | `isUUID` | Must be a valid UUID |
332
+ | `isInt` | Must be an integer |
333
+ | `isFloat` | Must be a float |
334
+ | `isDate` | Must be a valid date |
335
+ | `isBoolean` | Must be a boolean |
336
+ | `isAlpha` | Letters only |
337
+ | `isAlphanumeric` | Letters and digits only |
338
+ | `isNotEmpty` | Must not be empty |
339
+ | `isIn(a,b,c)` | Must be one of the listed values |
340
+ | `minLength(n)` | Minimum string length |
341
+ | `maxLength(n)` | Maximum string length |
342
+ | `min(n)` | Minimum numeric value |
343
+ | `max(n)` | Maximum numeric value |
344
+ | `default(val)` | Default value |
345
+ | `matches(regex)` | Must match regex pattern |
346
+
347
+ **Description** — any tokens that don't match a type or a rule are treated as free-text description, joined with `, `.
348
+
349
+ ```
350
+ search: string, tìm theo tên hoặc email ← "tìm theo tên hoặc email" is description
351
+ limit: number, default(20), max(100) ← all three are rules
352
+ Authorization: required, Bearer {token} ← "Bearer {token}" is description
83
353
  ```
84
354
 
85
- ## Using the UI
355
+ ---
86
356
 
87
- 1. **Select a route** from the sidebar (use the search bar to filter)
88
- 2. **Fill in parameters** — path params are auto-detected and shown first
89
- 3. **Set headers / auth / body** using the tabs in the request panel
90
- 4. **Click Send** (or press `Ctrl+Enter` / `Cmd+Enter`)
91
- 5. **Inspect the response** — body with JSON highlighting, status, timing, and headers
357
+ ## Module options
92
358
 
93
- ### Auth tab
359
+ ```typescript
360
+ ApiExplorerModule.register({
361
+ // URL path for the explorer UI
362
+ // Default: 'api-explorer'
363
+ path: 'api-explorer',
364
+
365
+ // Title shown in browser tab and UI header
366
+ // Default: 'API Explorer'
367
+ title: 'My API — Explorer',
368
+
369
+ // Path to the markdown docs file
370
+ // Relative paths are resolved from process.cwd()
371
+ // Default: null (no docs, routes still shown without documentation)
372
+ docsFile: './api-explorer.md',
373
+
374
+ // Explicitly enable or disable
375
+ // The explorer is ALWAYS disabled when NODE_ENV === 'production'
376
+ // Default: true
377
+ enabled: true,
378
+ })
379
+ ```
94
380
 
95
- | Type | Description |
96
- |---------------|----------------------------------------------------------|
97
- | None | No Authorization header |
98
- | Bearer Token | Adds `Authorization: Bearer <token>` |
99
- | Basic Auth | Adds `Authorization: Basic <base64(user:pass)>` |
100
- | API Key | Adds a custom key to a header or as a query parameter |
381
+ ---
101
382
 
102
- ### Body tab
383
+ ## Security
103
384
 
104
- | Type | Content-Type |
105
- |--------|-------------------------------------|
106
- | None | No body sent |
107
- | JSON | `application/json` + Format button |
108
- | Form | `application/x-www-form-urlencoded` |
109
- | Text | `text/plain` |
385
+ The explorer is **automatically disabled** when `NODE_ENV === 'production'`. No routes are registered and no UI is served.
110
386
 
111
- ## Peer dependencies
387
+ For extra protection in staging environments, use the `enabled` option:
112
388
 
113
- ```json
114
- {
115
- "@nestjs/common": ">=9.0.0",
116
- "@nestjs/core": ">=9.0.0"
117
- }
389
+ ```typescript
390
+ ApiExplorerModule.register({
391
+ enabled: process.env.ENABLE_API_EXPLORER === 'true',
392
+ })
118
393
  ```
119
394
 
120
- ## How it works
395
+ ---
121
396
 
122
- `ApiExplorerService` uses NestJS's `HttpAdapterHost` to access the underlying Express app instance and traverses `app._router.stack` to discover all registered routes at request time. This means the route list is always up-to-date with whatever is actually mounted on the Express router.
397
+ ## Routes exposed by the module
123
398
 
124
- ## Production safety
399
+ | Route | Description |
400
+ |-------|-------------|
401
+ | `GET /{path}` | Serves the explorer UI (HTML) |
402
+ | `GET /{path}/routes` | Returns route list as JSON |
125
403
 
126
- The module automatically returns an empty `DynamicModule` (no controllers, no providers) when `NODE_ENV === 'production'`, so there is no runtime overhead and no routes are exposed.
404
+ The JSON response from `/routes` looks like:
127
405
 
128
- You can also disable it explicitly:
406
+ ```json
407
+ [
408
+ {
409
+ "method": "POST",
410
+ "path": "/auth/login",
411
+ "params": [],
412
+ "group": "Auth",
413
+ "description": "Đăng nhập và nhận JWT token",
414
+ "body": [
415
+ { "name": "email", "type": "string", "required": true, "rules": ["isEmail"], "description": "" },
416
+ { "name": "password", "type": "string", "required": true, "rules": ["minLength(6)"], "description": "" }
417
+ ],
418
+ "query": [],
419
+ "headers": [],
420
+ "response": [
421
+ { "name": "token", "type": "string", "required": false, "rules": [], "description": "" }
422
+ ]
423
+ }
424
+ ]
425
+ ```
426
+
427
+ ---
428
+
429
+ ## TypeScript types
129
430
 
130
431
  ```typescript
131
- ApiExplorerModule.register({ enabled: false })
432
+ import {
433
+ ApiExplorerModule,
434
+ ApiExplorerOptions,
435
+ ApiExplorerService,
436
+ RouteInfo,
437
+ DocField,
438
+ parseDocsFile,
439
+ } from '@vnphu/nestjs-api-explorer';
440
+
441
+ // DocField — one field in body/query/params/headers/response
442
+ interface DocField {
443
+ name: string;
444
+ type: string; // 'string' | 'number' | 'boolean' | 'array' | 'object' | ''
445
+ required: boolean;
446
+ rules: string[];
447
+ description: string;
448
+ }
449
+
450
+ // RouteInfo — one route returned by /routes endpoint
451
+ interface RouteInfo {
452
+ method: string;
453
+ path: string;
454
+ params: string[]; // path param names, e.g. ['id', 'orderId']
455
+ group: string;
456
+ description: string;
457
+ body: DocField[];
458
+ query: DocField[];
459
+ headers: DocField[];
460
+ response: DocField[];
461
+ }
132
462
  ```
133
463
 
464
+ ---
465
+
134
466
  ## License
135
467
 
136
468
  MIT
@@ -0,0 +1,24 @@
1
+ export interface DocField {
2
+ name: string;
3
+ type: string;
4
+ required: boolean;
5
+ rules: string[];
6
+ description: string;
7
+ }
8
+ export interface RouteDoc {
9
+ method: string;
10
+ path: string;
11
+ group: string;
12
+ description: string;
13
+ body: DocField[];
14
+ query: DocField[];
15
+ params: DocField[];
16
+ headers: DocField[];
17
+ response: DocField[];
18
+ }
19
+ /**
20
+ * Parse a full docs file (absolute path) into a map of
21
+ * `"METHOD:path"` → RouteDoc.
22
+ */
23
+ export declare function parseDocsFile(filePath: string): Map<string, RouteDoc>;
24
+ //# sourceMappingURL=api-docs-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-docs-parser.d.ts","sourceRoot":"","sources":["../src/api-docs-parser.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,QAAQ,EAAE,QAAQ,EAAE,CAAC;CACtB;AAyGD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAmFrE"}