exisjs 0.7.7 → 0.7.8
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.
|
@@ -138,7 +138,7 @@ export const UserParamsSchema = tex.object({ id: tex.string() });
|
|
|
138
138
|
```
|
|
139
139
|
|
|
140
140
|
`tex` is the only validation engine. Schemas here are also the source for
|
|
141
|
-
OpenAPI generation
|
|
141
|
+
OpenAPI generation via `exisjs/swagger`.
|
|
142
142
|
|
|
143
143
|
---
|
|
144
144
|
|
|
@@ -5,7 +5,7 @@ nextTitle: "Swagger API Reference"
|
|
|
5
5
|
|
|
6
6
|
# Swagger & OpenAPI 3.1 Documentation
|
|
7
7
|
|
|
8
|
-
ExisJS comes with a native,
|
|
8
|
+
ExisJS comes with a native, built-in **Swagger & OpenAPI 3.1** documentation engine. It automatically discovers all registered routes, introspects **Tex** and **Zod** validation schemas, and serves interactive API documentation at `/docs`.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -44,6 +44,8 @@ The industry-standard interactive Swagger explorer featuring **Try it out**, liv
|
|
|
44
44
|
A modern, high-performance, dark-mode-first API reference documentation viewer with integrated code snippets and fast search.
|
|
45
45
|
|
|
46
46
|
```typescript filename="exis.config.ts"
|
|
47
|
+
import { defineConfig } from 'exisjs/config'
|
|
48
|
+
|
|
47
49
|
export default defineConfig({
|
|
48
50
|
docs: {
|
|
49
51
|
enabled: true,
|
|
@@ -56,14 +58,14 @@ export default defineConfig({
|
|
|
56
58
|
|
|
57
59
|
## Documenting Functional Routes
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
In ExisJS folder-based routing, each endpoint folder contains a `route.ts` file that exports a default `controller({ ... })`. You can add OpenAPI metadata directly to each route definition:
|
|
60
62
|
|
|
61
63
|
```typescript filename="src/http/users/route.ts"
|
|
62
64
|
import { controller, route } from 'exisjs/router'
|
|
63
65
|
import { tex, getPaginationSkip } from 'exisjs/validator'
|
|
64
66
|
|
|
65
67
|
export default controller({
|
|
66
|
-
|
|
68
|
+
list: route.get('/', {
|
|
67
69
|
summary: 'List active users',
|
|
68
70
|
description: 'Returns a paginated list of all active users in the system.',
|
|
69
71
|
tags: ['Users'],
|
|
@@ -80,11 +82,19 @@ export default controller({
|
|
|
80
82
|
},
|
|
81
83
|
async handle({ query }) {
|
|
82
84
|
const { skip, limit } = getPaginationSkip(query)
|
|
83
|
-
return
|
|
85
|
+
return {
|
|
86
|
+
data: [
|
|
87
|
+
{ id: '550e8400-e29b-41d4-a716-446655440000', name: 'Alice' },
|
|
88
|
+
{ id: '550e8400-e29b-41d4-a716-446655440001', name: 'Bob' },
|
|
89
|
+
],
|
|
90
|
+
total: 2,
|
|
91
|
+
skip,
|
|
92
|
+
limit,
|
|
93
|
+
}
|
|
84
94
|
},
|
|
85
95
|
}),
|
|
86
96
|
|
|
87
|
-
|
|
97
|
+
create: route.post('/', {
|
|
88
98
|
summary: 'Create a new user',
|
|
89
99
|
tags: ['Users'],
|
|
90
100
|
body: tex.object({
|
|
@@ -96,11 +106,11 @@ export default controller({
|
|
|
96
106
|
400: { description: 'Validation error' },
|
|
97
107
|
},
|
|
98
108
|
async handle({ body }) {
|
|
99
|
-
return
|
|
109
|
+
return { id: '550e8400-e29b-41d4-a716-446655440002', ...body }
|
|
100
110
|
},
|
|
101
111
|
}),
|
|
102
112
|
|
|
103
|
-
|
|
113
|
+
getById: route.get('/:id', {
|
|
104
114
|
summary: 'Get user by UUID',
|
|
105
115
|
tags: ['Users'],
|
|
106
116
|
params: tex.object({ id: tex.uuid() }),
|
|
@@ -109,7 +119,7 @@ export default controller({
|
|
|
109
119
|
404: { description: 'User not found' },
|
|
110
120
|
},
|
|
111
121
|
async handle({ params }) {
|
|
112
|
-
return
|
|
122
|
+
return { id: params.id, name: 'Alice' }
|
|
113
123
|
},
|
|
114
124
|
}),
|
|
115
125
|
})
|
|
@@ -119,9 +129,9 @@ export default controller({
|
|
|
119
129
|
|
|
120
130
|
## Documenting Class-Based Controllers (OOP)
|
|
121
131
|
|
|
122
|
-
For class-based controllers
|
|
132
|
+
For class-based controllers in `route.ts`, use OpenAPI decorators from `exisjs/swagger` alongside standard routing decorators:
|
|
123
133
|
|
|
124
|
-
```typescript filename="src/http/products/
|
|
134
|
+
```typescript filename="src/http/products/route.ts"
|
|
125
135
|
import { Controller, Get, Post, Body, Param, Query } from 'exisjs/decorators'
|
|
126
136
|
import {
|
|
127
137
|
ApiTags,
|
|
@@ -130,32 +140,42 @@ import {
|
|
|
130
140
|
ApiBearerAuth,
|
|
131
141
|
ApiExclude,
|
|
132
142
|
} from 'exisjs/swagger'
|
|
133
|
-
import { tex } from 'exisjs/validator'
|
|
134
143
|
|
|
135
144
|
@ApiTags('Products')
|
|
136
145
|
@ApiBearerAuth()
|
|
137
|
-
@Controller(
|
|
138
|
-
export class
|
|
146
|
+
@Controller()
|
|
147
|
+
export default class ProductsController {
|
|
139
148
|
@Get('/')
|
|
140
149
|
@ApiOperation({
|
|
141
150
|
summary: 'List products',
|
|
142
151
|
description: 'Fetches paginated list of catalog products',
|
|
143
152
|
})
|
|
144
153
|
@ApiResponse({ status: 200, description: 'List of products' })
|
|
145
|
-
async list(@Query('page') page: number) {
|
|
146
|
-
return
|
|
154
|
+
async list(@Query('page') page: number = 1) {
|
|
155
|
+
return [
|
|
156
|
+
{ id: 1, name: 'Mechanical Keyboard', price: 99 },
|
|
157
|
+
{ id: 2, name: 'Ergonomic Mouse', price: 49 },
|
|
158
|
+
]
|
|
147
159
|
}
|
|
148
160
|
|
|
149
161
|
@Post('/')
|
|
150
162
|
@ApiOperation({ summary: 'Create new product' })
|
|
151
163
|
@ApiResponse({ status: 201, description: 'Product created' })
|
|
152
|
-
async create(@Body() dto:
|
|
153
|
-
return
|
|
164
|
+
async create(@Body() dto: { name: string; price: number }) {
|
|
165
|
+
return { id: 3, ...dto }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@Get('/:id')
|
|
169
|
+
@ApiOperation({ summary: 'Get product by ID' })
|
|
170
|
+
@ApiResponse({ status: 200, description: 'Product found' })
|
|
171
|
+
@ApiResponse({ status: 404, description: 'Product not found' })
|
|
172
|
+
async getById(@Param('id') id: string) {
|
|
173
|
+
return { id, name: 'Mechanical Keyboard', price: 99 }
|
|
154
174
|
}
|
|
155
175
|
|
|
156
|
-
@Get('/internal
|
|
176
|
+
@Get('/internal/metrics')
|
|
157
177
|
@ApiExclude() // Excluded from Swagger documentation
|
|
158
|
-
async
|
|
178
|
+
async metrics() {
|
|
159
179
|
return { uptime: process.uptime() }
|
|
160
180
|
}
|
|
161
181
|
}
|
|
@@ -165,7 +185,7 @@ export class ProductController {
|
|
|
165
185
|
|
|
166
186
|
## Cascading Boundary Metadata
|
|
167
187
|
|
|
168
|
-
Cascading boundaries (`boundary.ts`) can assign shared tags, descriptions, or security requirements to all nested routes:
|
|
188
|
+
Cascading boundaries (`boundary.ts`) can assign shared tags, descriptions, or security requirements to all nested routes in the directory:
|
|
169
189
|
|
|
170
190
|
```typescript filename="src/http/admin/boundary.ts"
|
|
171
191
|
import { defineBoundary } from 'exisjs/router'
|
|
@@ -183,6 +203,8 @@ export const config = defineBoundary({
|
|
|
183
203
|
Define reusable security schemes (such as Bearer JWT, API Keys, or OAuth2) in `exis.config.ts`:
|
|
184
204
|
|
|
185
205
|
```typescript filename="exis.config.ts"
|
|
206
|
+
import { defineConfig } from 'exisjs/config'
|
|
207
|
+
|
|
186
208
|
export default defineConfig({
|
|
187
209
|
docs: {
|
|
188
210
|
enabled: true,
|
|
@@ -208,21 +230,60 @@ export default defineConfig({
|
|
|
208
230
|
|
|
209
231
|
---
|
|
210
232
|
|
|
211
|
-
##
|
|
233
|
+
## Advanced Configuration (`exis.config.ts`)
|
|
212
234
|
|
|
213
|
-
|
|
235
|
+
For advanced customization, such as modifying the generated OpenAPI spec before serving or injecting custom styling:
|
|
214
236
|
|
|
215
|
-
```typescript filename="
|
|
216
|
-
import {
|
|
217
|
-
import { swagger } from 'exisjs/swagger'
|
|
237
|
+
```typescript filename="exis.config.ts"
|
|
238
|
+
import { defineConfig } from 'exisjs/config'
|
|
218
239
|
|
|
219
|
-
|
|
240
|
+
export default defineConfig({
|
|
241
|
+
docs: {
|
|
242
|
+
enabled: true,
|
|
243
|
+
title: 'Enterprise API Gateway',
|
|
244
|
+
version: '2.0.0',
|
|
245
|
+
description: 'Internal and external microservices API reference',
|
|
246
|
+
ui: 'scalar', // 'swagger-ui' | 'scalar'
|
|
247
|
+
servers: [
|
|
248
|
+
{ url: 'https://api.example.com', description: 'Production Server' },
|
|
249
|
+
{ url: 'http://localhost:3000', description: 'Local Development' },
|
|
250
|
+
],
|
|
251
|
+
transformSpec(spec) {
|
|
252
|
+
// Programmatically mutate or enrich OpenAPI specification
|
|
253
|
+
spec.info.contact = { name: 'API Support', email: 'support@example.com' }
|
|
254
|
+
return spec
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
})
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Server Lifecycle Hook (`server.ts`)
|
|
263
|
+
|
|
264
|
+
You can also interact with the Swagger subsystem or application instance during server boot in `src/http/server.ts`:
|
|
265
|
+
|
|
266
|
+
### Functional Paradigm
|
|
267
|
+
```typescript filename="src/http/server.ts"
|
|
268
|
+
import { exis } from 'exisjs'
|
|
269
|
+
|
|
270
|
+
export default exis({
|
|
271
|
+
onStart(app) {
|
|
272
|
+
console.log('[API] Swagger documentation ready at /docs')
|
|
273
|
+
},
|
|
274
|
+
})
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Class-Based (OOP) Paradigm
|
|
278
|
+
```typescript filename="src/http/server.ts"
|
|
279
|
+
import { Server } from 'exisjs/decorators'
|
|
280
|
+
import type { App } from 'exisjs'
|
|
220
281
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
})(app)
|
|
282
|
+
@Server()
|
|
283
|
+
export default class RootServer {
|
|
284
|
+
async onStart(app: App) {
|
|
285
|
+
console.log('[API] Swagger documentation ready at /docs')
|
|
286
|
+
}
|
|
287
|
+
}
|
|
228
288
|
```
|
|
289
|
+
|
|
@@ -53,91 +53,174 @@ import {
|
|
|
53
53
|
Assigns one or more tags to a controller class or method handler.
|
|
54
54
|
*(Alias: `@Tags()`)*
|
|
55
55
|
|
|
56
|
-
```typescript
|
|
56
|
+
```typescript filename="src/http/users/route.ts"
|
|
57
|
+
import { Controller, Get } from 'exisjs/decorators'
|
|
58
|
+
import { ApiTags } from 'exisjs/swagger'
|
|
59
|
+
|
|
57
60
|
@ApiTags('Users', 'Authentication')
|
|
58
|
-
@Controller(
|
|
59
|
-
export class UserController {
|
|
61
|
+
@Controller()
|
|
62
|
+
export default class UserController {
|
|
63
|
+
@Get('/')
|
|
64
|
+
findAll() {
|
|
65
|
+
return []
|
|
66
|
+
}
|
|
67
|
+
}
|
|
60
68
|
```
|
|
61
69
|
|
|
62
70
|
### `@ApiOperation(options)`
|
|
63
71
|
Attaches summary, description, operationId, and deprecated status to a route method.
|
|
64
72
|
*(Alias: `@Operation()`)*
|
|
65
73
|
|
|
66
|
-
```typescript
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
```typescript filename="src/http/users/route.ts"
|
|
75
|
+
import { Controller, Get, Param } from 'exisjs/decorators'
|
|
76
|
+
import { ApiOperation } from 'exisjs/swagger'
|
|
77
|
+
|
|
78
|
+
@Controller()
|
|
79
|
+
export default class UserController {
|
|
80
|
+
@Get('/:id')
|
|
81
|
+
@ApiOperation({
|
|
82
|
+
summary: 'Get user by UUID',
|
|
83
|
+
description: 'Fetches user details from database',
|
|
84
|
+
deprecated: false,
|
|
85
|
+
})
|
|
86
|
+
getUser(@Param('id') id: string) {
|
|
87
|
+
return { id, name: 'Alice' }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
74
90
|
```
|
|
75
91
|
|
|
76
92
|
### `@ApiResponse(options)`
|
|
77
93
|
Defines an expected HTTP response status code and optional schema.
|
|
78
94
|
|
|
79
|
-
```typescript
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
```typescript filename="src/http/users/route.ts"
|
|
96
|
+
import { Controller, Get, Param } from 'exisjs/decorators'
|
|
97
|
+
import { ApiOperation, ApiResponse } from 'exisjs/swagger'
|
|
98
|
+
import { tex } from 'exisjs/validator'
|
|
99
|
+
|
|
100
|
+
const UserSchema = tex.object({
|
|
101
|
+
id: tex.uuid(),
|
|
102
|
+
name: tex.string(),
|
|
84
103
|
})
|
|
85
|
-
|
|
86
|
-
|
|
104
|
+
|
|
105
|
+
@Controller()
|
|
106
|
+
export default class UserController {
|
|
107
|
+
@Get('/:id')
|
|
108
|
+
@ApiResponse({
|
|
109
|
+
status: 200,
|
|
110
|
+
description: 'User details found',
|
|
111
|
+
schema: UserSchema,
|
|
112
|
+
})
|
|
113
|
+
@ApiResponse({ status: 404, description: 'User not found' })
|
|
114
|
+
getUser(@Param('id') id: string) {
|
|
115
|
+
return { id, name: 'Alice' }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
87
118
|
```
|
|
88
119
|
|
|
89
120
|
### `@ApiBearerAuth(name?: string)`
|
|
90
121
|
Requires Bearer JWT authentication for this endpoint or controller.
|
|
91
122
|
*(Alias: `@BearerAuth()`)*
|
|
92
123
|
|
|
93
|
-
```typescript
|
|
124
|
+
```typescript filename="src/http/users/route.ts"
|
|
125
|
+
import { Controller, Get } from 'exisjs/decorators'
|
|
126
|
+
import { ApiBearerAuth } from 'exisjs/swagger'
|
|
127
|
+
|
|
94
128
|
@ApiBearerAuth()
|
|
95
|
-
@
|
|
96
|
-
|
|
129
|
+
@Controller()
|
|
130
|
+
export default class UserController {
|
|
131
|
+
@Get('/me')
|
|
132
|
+
getMe() {
|
|
133
|
+
return { id: 'user-123', role: 'admin' }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
97
136
|
```
|
|
98
137
|
|
|
99
138
|
### `@ApiSecurity(name: string, scopes?: string[])`
|
|
100
139
|
Requires custom security scheme authentication for this endpoint or controller.
|
|
101
140
|
|
|
102
|
-
```typescript
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
141
|
+
```typescript filename="src/http/metrics/route.ts"
|
|
142
|
+
import { Controller, Get } from 'exisjs/decorators'
|
|
143
|
+
import { ApiSecurity } from 'exisjs/swagger'
|
|
144
|
+
|
|
145
|
+
@Controller()
|
|
146
|
+
export default class MetricsController {
|
|
147
|
+
@Get('/')
|
|
148
|
+
@ApiSecurity('apiKeyAuth')
|
|
149
|
+
getMetrics() {
|
|
150
|
+
return { uptime: process.uptime() }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
106
153
|
```
|
|
107
154
|
|
|
108
155
|
### `@ApiExclude()`
|
|
109
156
|
Excludes an endpoint or controller class from OpenAPI and Swagger generation.
|
|
110
157
|
|
|
111
|
-
```typescript
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
158
|
+
```typescript filename="src/http/internal/route.ts"
|
|
159
|
+
import { Controller, Get } from 'exisjs/decorators'
|
|
160
|
+
import { ApiExclude } from 'exisjs/swagger'
|
|
161
|
+
|
|
162
|
+
@Controller()
|
|
163
|
+
export default class InternalController {
|
|
164
|
+
@Get('/health')
|
|
165
|
+
@ApiExclude()
|
|
166
|
+
getHealth() {
|
|
167
|
+
return { status: 'healthy' }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
115
170
|
```
|
|
116
171
|
|
|
117
172
|
### `@ApiProperty(options?: { description?: string; example?: any; required?: boolean })`
|
|
118
173
|
Annotates a class property with metadata for OpenAPI schema models.
|
|
119
174
|
|
|
175
|
+
```typescript filename="src/http/users/dto.ts"
|
|
176
|
+
import { ApiProperty } from 'exisjs/swagger'
|
|
177
|
+
|
|
178
|
+
export class CreateUserDto {
|
|
179
|
+
@ApiProperty({ description: 'Full name of the user', example: 'Alice Johnson', required: true })
|
|
180
|
+
name!: string
|
|
181
|
+
|
|
182
|
+
@ApiProperty({ description: 'Work email address', example: 'alice@example.com', required: true })
|
|
183
|
+
email!: string
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
120
187
|
---
|
|
121
188
|
|
|
122
189
|
## Functions
|
|
123
190
|
|
|
124
191
|
### `swagger(config?: SwaggerConfig)`
|
|
125
|
-
Convenience helper to mount Swagger/OpenAPI documentation on an ExisJS App instance.
|
|
192
|
+
Convenience helper to mount Swagger/OpenAPI documentation on an ExisJS App instance (typically inside `onStart` or custom server setups).
|
|
126
193
|
|
|
127
|
-
```typescript
|
|
128
|
-
import {
|
|
194
|
+
```typescript filename="src/http/server.ts"
|
|
195
|
+
import { exis } from 'exisjs'
|
|
129
196
|
import { swagger } from 'exisjs/swagger'
|
|
130
197
|
|
|
131
|
-
|
|
132
|
-
|
|
198
|
+
export default exis({
|
|
199
|
+
onStart(app) {
|
|
200
|
+
swagger({ title: 'My API', path: '/docs' })(app)
|
|
201
|
+
},
|
|
202
|
+
})
|
|
133
203
|
```
|
|
134
204
|
|
|
135
205
|
### `generateOpenApiSpec(routesOrApp, config)`
|
|
136
|
-
Pure generator function that introspects routes, parameters,
|
|
206
|
+
Pure generator function that introspects routes, parameters, and `TexEngine`/Zod schemas and returns a complete `OpenApiSpec` object.
|
|
137
207
|
|
|
138
208
|
```typescript
|
|
209
|
+
import { generateOpenApiSpec } from 'exisjs/swagger'
|
|
210
|
+
|
|
139
211
|
const spec = generateOpenApiSpec(app, { title: 'My API', version: '1.0.0' })
|
|
140
212
|
```
|
|
141
213
|
|
|
142
214
|
### `renderDocumentationHtml(config)`
|
|
143
215
|
Renders standalone HTML markup for Swagger UI or Scalar API reference.
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
import { renderDocumentationHtml } from 'exisjs/swagger'
|
|
219
|
+
|
|
220
|
+
const html = renderDocumentationHtml({
|
|
221
|
+
title: 'My API',
|
|
222
|
+
specPath: '/docs/json',
|
|
223
|
+
ui: 'scalar',
|
|
224
|
+
})
|
|
225
|
+
```
|
|
226
|
+
|
|
@@ -234,7 +234,7 @@ type Profile = ResolveSchema<typeof ProfileSchema>
|
|
|
234
234
|
|
|
235
235
|
## OpenAPI Generation
|
|
236
236
|
|
|
237
|
-
The `TexEngine` automatically converts validation schemas into strictly compliant OpenAPI/Swagger JSON schemas when attached to routes. This powers zero-config Swagger documentation
|
|
237
|
+
The `TexEngine` automatically converts validation schemas into strictly compliant OpenAPI/Swagger JSON schemas when attached to routes. This powers zero-config Swagger documentation in `exisjs/swagger`.
|
|
238
238
|
|
|
239
239
|
---
|
|
240
240
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "exisjs",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
4
4
|
"description": "efficient full-stack web framework for Node.js",
|
|
5
5
|
"author": "V25 Group <support@v25group.com>",
|
|
6
6
|
"keywords": [
|
|
@@ -84,6 +84,10 @@
|
|
|
84
84
|
"./logger": {
|
|
85
85
|
"types": "./dist/logger/index.d.ts",
|
|
86
86
|
"default": "./dist/logger/index.js"
|
|
87
|
+
},
|
|
88
|
+
"./swagger": {
|
|
89
|
+
"types": "./dist/swagger/index.d.ts",
|
|
90
|
+
"default": "./dist/swagger/index.js"
|
|
87
91
|
}
|
|
88
92
|
},
|
|
89
93
|
"files": [
|