@venizia/ignis-docs 0.0.7 → 0.0.8-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/dist/mcp-server/common/paths.d.ts +4 -2
- package/dist/mcp-server/common/paths.d.ts.map +1 -1
- package/dist/mcp-server/common/paths.js +8 -6
- package/dist/mcp-server/common/paths.js.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/docs/get-document-content.tool.js +7 -7
- package/dist/mcp-server/tools/docs/get-document-metadata.tool.js +3 -3
- package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts +1 -1
- package/dist/mcp-server/tools/docs/get-package-overview.tool.js +1 -1
- package/package.json +1 -1
- package/wiki/best-practices/api-usage-examples.md +9 -9
- package/wiki/best-practices/architectural-patterns.md +19 -3
- package/wiki/best-practices/architecture-decisions.md +6 -6
- package/wiki/best-practices/code-style-standards/advanced-patterns.md +1 -1
- package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
- package/wiki/best-practices/code-style-standards/function-patterns.md +2 -2
- package/wiki/best-practices/code-style-standards/index.md +2 -2
- package/wiki/best-practices/code-style-standards/naming-conventions.md +1 -1
- package/wiki/best-practices/code-style-standards/route-definitions.md +4 -4
- package/wiki/best-practices/data-modeling.md +1 -1
- package/wiki/best-practices/deployment-strategies.md +1 -1
- package/wiki/best-practices/error-handling.md +2 -2
- package/wiki/best-practices/performance-optimization.md +3 -3
- package/wiki/best-practices/security-guidelines.md +2 -2
- package/wiki/best-practices/troubleshooting-tips.md +1 -1
- package/wiki/{references → extensions}/components/authentication/api.md +12 -20
- package/wiki/{references → extensions}/components/authentication/errors.md +1 -1
- package/wiki/{references → extensions}/components/authentication/index.md +5 -8
- package/wiki/{references → extensions}/components/authentication/usage.md +20 -36
- package/wiki/{references → extensions}/components/authorization/api.md +62 -13
- package/wiki/{references → extensions}/components/authorization/errors.md +12 -7
- package/wiki/{references → extensions}/components/authorization/index.md +93 -6
- package/wiki/{references → extensions}/components/authorization/usage.md +42 -4
- package/wiki/{references → extensions}/components/health-check.md +5 -4
- package/wiki/{references → extensions}/components/index.md +2 -0
- package/wiki/{references → extensions}/components/mail/index.md +1 -1
- package/wiki/{references → extensions}/components/request-tracker.md +1 -1
- package/wiki/{references → extensions}/components/socket-io/api.md +2 -2
- package/wiki/{references → extensions}/components/socket-io/errors.md +2 -0
- package/wiki/{references → extensions}/components/socket-io/index.md +24 -20
- package/wiki/{references → extensions}/components/socket-io/usage.md +2 -2
- package/wiki/{references → extensions}/components/static-asset/api.md +14 -15
- package/wiki/{references → extensions}/components/static-asset/errors.md +3 -1
- package/wiki/{references → extensions}/components/static-asset/index.md +158 -89
- package/wiki/{references → extensions}/components/static-asset/usage.md +8 -5
- package/wiki/{references → extensions}/components/swagger.md +3 -3
- package/wiki/{references → extensions}/components/template/index.md +4 -4
- package/wiki/{references → extensions}/components/template/setup-page.md +1 -1
- package/wiki/{references → extensions}/components/template/single-page.md +1 -1
- package/wiki/{references → extensions}/components/websocket/api.md +7 -6
- package/wiki/{references → extensions}/components/websocket/errors.md +17 -3
- package/wiki/{references → extensions}/components/websocket/index.md +17 -11
- package/wiki/{references → extensions}/components/websocket/usage.md +2 -2
- package/wiki/{references → extensions}/helpers/crypto/index.md +1 -1
- package/wiki/{references → extensions}/helpers/env/index.md +9 -5
- package/wiki/{references → extensions}/helpers/error/index.md +2 -7
- package/wiki/{references → extensions}/helpers/index.md +18 -6
- package/wiki/{references → extensions}/helpers/kafka/admin.md +13 -1
- package/wiki/{references → extensions}/helpers/kafka/consumer.md +28 -28
- package/wiki/{references → extensions}/helpers/kafka/examples.md +19 -19
- package/wiki/{references → extensions}/helpers/kafka/index.md +51 -48
- package/wiki/{references → extensions}/helpers/kafka/producer.md +18 -18
- package/wiki/{references → extensions}/helpers/kafka/schema-registry.md +25 -25
- package/wiki/{references → extensions}/helpers/logger/index.md +2 -2
- package/wiki/{references → extensions}/helpers/queue/index.md +400 -4
- package/wiki/{references → extensions}/helpers/storage/api.md +170 -10
- package/wiki/{references → extensions}/helpers/storage/index.md +44 -8
- package/wiki/{references → extensions}/helpers/template/index.md +1 -1
- package/wiki/{references → extensions}/helpers/testing/index.md +4 -4
- package/wiki/{references → extensions}/helpers/types/index.md +63 -16
- package/wiki/{references → extensions}/helpers/websocket/index.md +1 -1
- package/wiki/extensions/index.md +48 -0
- package/wiki/guides/core-concepts/application/bootstrapping.md +55 -37
- package/wiki/guides/core-concepts/application/index.md +95 -35
- package/wiki/guides/core-concepts/components-guide.md +23 -19
- package/wiki/guides/core-concepts/components.md +34 -10
- package/wiki/guides/core-concepts/dependency-injection.md +99 -34
- package/wiki/guides/core-concepts/grpc-controllers.md +295 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +27 -8
- package/wiki/guides/core-concepts/persistent/models.md +43 -1
- package/wiki/guides/core-concepts/persistent/repositories.md +75 -8
- package/wiki/guides/core-concepts/persistent/transactions.md +38 -8
- package/wiki/guides/core-concepts/{controllers.md → rest-controllers.md} +30 -33
- package/wiki/guides/core-concepts/services.md +19 -5
- package/wiki/guides/get-started/5-minute-quickstart.md +6 -7
- package/wiki/guides/get-started/philosophy.md +1 -1
- package/wiki/guides/index.md +2 -2
- package/wiki/guides/reference/glossary.md +7 -7
- package/wiki/guides/reference/mcp-docs-server.md +1 -1
- package/wiki/guides/tutorials/building-a-crud-api.md +2 -2
- package/wiki/guides/tutorials/complete-installation.md +17 -14
- package/wiki/guides/tutorials/ecommerce-api.md +18 -18
- package/wiki/guides/tutorials/realtime-chat.md +8 -8
- package/wiki/guides/tutorials/testing.md +2 -2
- package/wiki/index.md +4 -3
- package/wiki/references/base/application.md +341 -21
- package/wiki/references/base/bootstrapping.md +43 -13
- package/wiki/references/base/components.md +259 -8
- package/wiki/references/base/controllers.md +556 -253
- package/wiki/references/base/datasources.md +159 -79
- package/wiki/references/base/dependency-injection.md +299 -48
- package/wiki/references/base/filter-system/application-usage.md +18 -2
- package/wiki/references/base/filter-system/array-operators.md +14 -6
- package/wiki/references/base/filter-system/comparison-operators.md +9 -3
- package/wiki/references/base/filter-system/default-filter.md +28 -3
- package/wiki/references/base/filter-system/fields-order-pagination.md +17 -13
- package/wiki/references/base/filter-system/index.md +169 -11
- package/wiki/references/base/filter-system/json-filtering.md +51 -18
- package/wiki/references/base/filter-system/list-operators.md +4 -3
- package/wiki/references/base/filter-system/logical-operators.md +7 -2
- package/wiki/references/base/filter-system/null-operators.md +50 -0
- package/wiki/references/base/filter-system/quick-reference.md +82 -243
- package/wiki/references/base/filter-system/range-operators.md +7 -1
- package/wiki/references/base/filter-system/tips.md +34 -7
- package/wiki/references/base/filter-system/use-cases.md +6 -5
- package/wiki/references/base/grpc-controllers.md +984 -0
- package/wiki/references/base/index.md +32 -24
- package/wiki/references/base/middleware.md +347 -0
- package/wiki/references/base/models.md +390 -46
- package/wiki/references/base/providers.md +14 -14
- package/wiki/references/base/repositories/advanced.md +84 -69
- package/wiki/references/base/repositories/index.md +447 -12
- package/wiki/references/base/repositories/mixins.md +103 -98
- package/wiki/references/base/repositories/relations.md +129 -45
- package/wiki/references/base/repositories/soft-deletable.md +104 -23
- package/wiki/references/base/services.md +94 -14
- package/wiki/references/index.md +12 -10
- package/wiki/references/quick-reference.md +98 -65
- package/wiki/references/utilities/crypto.md +21 -4
- package/wiki/references/utilities/date.md +25 -7
- package/wiki/references/utilities/index.md +26 -24
- package/wiki/references/utilities/jsx.md +54 -54
- package/wiki/references/utilities/module.md +8 -6
- package/wiki/references/utilities/parse.md +16 -9
- package/wiki/references/utilities/performance.md +22 -7
- package/wiki/references/utilities/promise.md +19 -16
- package/wiki/references/utilities/request.md +48 -26
- package/wiki/references/utilities/schema.md +69 -6
- package/wiki/references/utilities/statuses.md +131 -140
- /package/wiki/{references → extensions}/components/mail/api.md +0 -0
- /package/wiki/{references → extensions}/components/mail/errors.md +0 -0
- /package/wiki/{references → extensions}/components/mail/usage.md +0 -0
- /package/wiki/{references → extensions}/components/template/api-page.md +0 -0
- /package/wiki/{references → extensions}/components/template/errors-page.md +0 -0
- /package/wiki/{references → extensions}/components/template/usage-page.md +0 -0
- /package/wiki/{references → extensions}/helpers/cron/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/inversion/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/network/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/network/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/redis/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/socket-io/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/socket-io/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/template/single-page.md +0 -0
- /package/wiki/{references → extensions}/helpers/uid/index.md +0 -0
- /package/wiki/{references → extensions}/helpers/websocket/api.md +0 -0
- /package/wiki/{references → extensions}/helpers/worker-thread/index.md +0 -0
- /package/wiki/{references → extensions}/src-details/mcp-server.md +0 -0
|
@@ -1,64 +1,422 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Controllers Reference
|
|
3
|
-
description: Technical reference for controller classes and API endpoints
|
|
2
|
+
title: REST Controllers Reference
|
|
3
|
+
description: Technical reference for REST controller classes and API endpoints
|
|
4
4
|
difficulty: beginner
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Deep Dive: Controllers
|
|
7
|
+
# Deep Dive: REST Controllers
|
|
8
8
|
|
|
9
|
-
Technical reference for controller classes - the foundation for creating API endpoints in Ignis.
|
|
9
|
+
Technical reference for REST controller classes - the foundation for creating HTTP/JSON API endpoints in Ignis.
|
|
10
|
+
|
|
11
|
+
> [!NOTE]
|
|
12
|
+
> This page covers **REST controllers** (HTTP/JSON). For gRPC controllers using ConnectRPC, see the [gRPC Controllers Reference](./grpc-controllers.md).
|
|
10
13
|
|
|
11
14
|
**Files:**
|
|
12
|
-
- `packages/core/src/base/controllers/abstract.ts`
|
|
13
|
-
- `packages/core/src/base/controllers/base.ts`
|
|
15
|
+
- `packages/core/src/base/controllers/rest/abstract.ts` - Abstract base class
|
|
16
|
+
- `packages/core/src/base/controllers/rest/base.ts` - Concrete base class
|
|
17
|
+
- `packages/core/src/base/controllers/common/types.ts` - Shared types and interfaces
|
|
18
|
+
- `packages/core/src/base/controllers/common/constants.ts` - Transport constants and headers
|
|
19
|
+
- `packages/core/src/base/metadata/routes/rest.ts` - Route decorators (`@api`, `@get`, `@post`, etc.)
|
|
20
|
+
- `packages/core/src/base/metadata/routes/controller.ts` - `@controller` decorator
|
|
21
|
+
- `packages/core/src/base/controllers/factory/controller.ts` - CRUD controller factory
|
|
22
|
+
- `packages/core/src/components/controller/rest/rest.component.ts` - RestComponent
|
|
14
23
|
|
|
15
24
|
## Quick Reference
|
|
16
25
|
|
|
17
|
-
| Class | Purpose |
|
|
18
|
-
|
|
19
|
-
| **
|
|
20
|
-
| **
|
|
26
|
+
| Class | Purpose | Key Methods |
|
|
27
|
+
|-------|---------|-------------|
|
|
28
|
+
| **AbstractRestController** | Base class with Hono router, auth middleware, and OpenAPI integration | `binding()`, `registerRoutesFromRegistry()`, `getRouteConfigs()`, `getJSXRouteConfigs()`, `buildRouteMiddlewares()` |
|
|
29
|
+
| **BaseRestController** | Concrete implementation with route registration methods | `defineRoute()`, `bindRoute()`, `defineJSXRoute()`, `toHonoHandler()` |
|
|
30
|
+
| **RestComponent** | Configures and mounts all REST controllers onto the application router | `binding()` |
|
|
31
|
+
| **ControllerFactory** | Generates typed CRUD controllers from entity definitions | `defineCrudController()` |
|
|
32
|
+
|
|
33
|
+
## Controller Transport System
|
|
34
|
+
|
|
35
|
+
Ignis supports multiple controller transports. The `@controller` decorator accepts a `transport` field to distinguish between REST and gRPC controllers.
|
|
36
|
+
|
|
37
|
+
### `ControllerTransports`
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
class ControllerTransports {
|
|
41
|
+
static readonly REST = 'rest';
|
|
42
|
+
static readonly GRPC = 'grpc';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type TControllerTransport = 'rest' | 'grpc';
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `TControllerMetadata`
|
|
49
|
+
|
|
50
|
+
The `@controller` decorator metadata is a union type:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
interface IBaseControllerMetadata {
|
|
54
|
+
path: string;
|
|
55
|
+
tags?: string[];
|
|
56
|
+
description?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface IRestControllerMetadata extends IBaseControllerMetadata {
|
|
60
|
+
transport?: typeof ControllerTransports.REST; // Optional — defaults to REST
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface IGrpcControllerMetadata<ServiceType = unknown> extends IBaseControllerMetadata {
|
|
64
|
+
transport: typeof ControllerTransports.GRPC; // Required for gRPC
|
|
65
|
+
service: ServiceType;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type TControllerMetadata = IRestControllerMetadata | IGrpcControllerMetadata;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
REST controllers do not need to specify `transport` explicitly — it defaults to REST when omitted.
|
|
72
|
+
|
|
73
|
+
### Application Transport Configuration
|
|
74
|
+
|
|
75
|
+
The application configures which transports to enable:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
class MyApp extends BaseApplication {
|
|
79
|
+
constructor() {
|
|
80
|
+
super({
|
|
81
|
+
// Defaults to ['rest'] if omitted
|
|
82
|
+
transports: [ControllerTransports.REST, ControllerTransports.GRPC],
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
During `registerControllers()`, the application creates a `RestComponent` for REST transport and a `GrpcComponent` for gRPC transport.
|
|
89
|
+
|
|
90
|
+
## `RestComponent`
|
|
91
|
+
|
|
92
|
+
**File:** `packages/core/src/components/controller/rest/rest.component.ts`
|
|
93
|
+
|
|
94
|
+
The `RestComponent` is responsible for discovering, configuring, and mounting all REST controllers onto the application's root Hono router. It is automatically instantiated by `BaseApplication.registerControllers()` when the REST transport is enabled.
|
|
95
|
+
|
|
96
|
+
### Behavior
|
|
97
|
+
|
|
98
|
+
1. Finds all bindings tagged with the `controllers` namespace
|
|
99
|
+
2. Skips any controller whose metadata has `transport: 'grpc'`
|
|
100
|
+
3. Validates that each remaining controller has a `path` in its metadata
|
|
101
|
+
4. Resolves each controller instance from the IoC container
|
|
102
|
+
5. Calls `configure()` on the controller (which runs `binding()` + `registerRoutesFromRegistry()`)
|
|
103
|
+
6. Mounts the controller's router at `metadata.path` on the application's root router
|
|
104
|
+
7. Dynamically re-fetches controller bindings after each mount to pick up any controllers added during configuration
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
export class RestComponent extends BaseComponent {
|
|
108
|
+
constructor(private application: BaseApplication) {
|
|
109
|
+
super({
|
|
110
|
+
scope: RestComponent.name,
|
|
111
|
+
initDefault: { enable: true, container: application },
|
|
112
|
+
bindings: {
|
|
113
|
+
[RestBindingKeys.REST_COMPONENT_OPTIONS]: Binding.bind<IRestComponentConfig>({
|
|
114
|
+
key: RestBindingKeys.REST_COMPONENT_OPTIONS,
|
|
115
|
+
}).toValue({}),
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
21
121
|
|
|
22
122
|
## Routing Approaches
|
|
23
123
|
|
|
24
124
|
| Approach | When to Use | Example |
|
|
25
125
|
|----------|-------------|---------|
|
|
26
126
|
| **Decorator-Based** (Recommended) | Clean, declarative routes | `@get({ configs: {...} })` |
|
|
27
|
-
| **
|
|
127
|
+
| **Imperative (`defineRoute`)** | Complex routing logic, feature flags | `this.defineRoute({ configs, handler })` |
|
|
128
|
+
| **Fluent (`bindRoute`)** | Two-step route registration | `this.bindRoute({ configs }).to({ handler })` |
|
|
129
|
+
| **JSX (`defineJSXRoute`)** | Server-rendered HTML pages | `this.defineJSXRoute({ configs, handler })` |
|
|
28
130
|
|
|
29
|
-
## `
|
|
131
|
+
## `AbstractRestController`
|
|
30
132
|
|
|
31
|
-
Base class integrating Hono routing with Ignis DI and OpenAPI generation.
|
|
133
|
+
Base class integrating Hono routing with Ignis DI, authentication/authorization middleware, and OpenAPI generation.
|
|
32
134
|
|
|
33
|
-
|
|
135
|
+
**Generic Parameters:**
|
|
34
136
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
137
|
+
```typescript
|
|
138
|
+
abstract class AbstractRestController<
|
|
139
|
+
RouteEnv extends Env = Env,
|
|
140
|
+
RouteSchema extends Schema = {},
|
|
141
|
+
BasePath extends string = '/',
|
|
142
|
+
ConfigurableOptions extends object = {},
|
|
143
|
+
Definitions extends Record<string, IAuthRouteConfig> = Record<string, IAuthRouteConfig>,
|
|
144
|
+
> extends BaseHelper implements IController<RouteEnv, RouteSchema, BasePath, ConfigurableOptions>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Constructor
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
constructor(opts: IControllerOptions)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
| Option | Type | Default | Description |
|
|
154
|
+
| :--- | :--- | :--- | :--- |
|
|
155
|
+
| `scope` | `string` | Required | Logger scope name |
|
|
156
|
+
| `path` | `string` | — | Route base path. Falls back to `@controller` decorator path if not provided |
|
|
157
|
+
| `isStrict` | `boolean` | `true` | When `true`, `/users` and `/users/` are different routes |
|
|
158
|
+
|
|
159
|
+
Path resolution priority: `@controller` decorator metadata > constructor `path` option. Throws if neither provides a path.
|
|
160
|
+
|
|
161
|
+
### Key Properties
|
|
162
|
+
|
|
163
|
+
| Property | Type | Description |
|
|
164
|
+
| :--- | :--- | :--- |
|
|
165
|
+
| `isConfigured` | `boolean` | Guards against double configuration |
|
|
166
|
+
| `router` | `OpenAPIHono` | The controller's Hono router instance |
|
|
167
|
+
| `path` | `string` | Resolved base path |
|
|
168
|
+
| `definitions` | `Definitions` | Route definition configs (used by factory-generated controllers) |
|
|
169
|
+
|
|
170
|
+
### Methods
|
|
171
|
+
|
|
172
|
+
#### `configure(opts?): Promise<OpenAPIHono>`
|
|
173
|
+
|
|
174
|
+
Configures the controller. Idempotent — returns the router immediately if already configured.
|
|
175
|
+
|
|
176
|
+
1. Calls `binding()` (your manual route definitions)
|
|
177
|
+
2. Calls `registerRoutesFromRegistry()` (decorator-based routes)
|
|
178
|
+
3. Sets `isConfigured = true`
|
|
179
|
+
|
|
180
|
+
#### `registerRoutesFromRegistry(): void`
|
|
181
|
+
|
|
182
|
+
Reads route metadata registered by `@get`, `@post`, `@api`, etc. decorators and binds them to the router using `bindRoute().to()`.
|
|
183
|
+
|
|
184
|
+
#### `getRouteConfigs<RouteConfig>(opts: { configs: RouteConfig })`
|
|
185
|
+
|
|
186
|
+
Processes a route config, injecting authentication/authorization middleware and OpenAPI security specs. Returns a Hono `createRoute` result. Automatically appends the controller's `scope` as a tag.
|
|
187
|
+
|
|
188
|
+
#### `getJSXRouteConfigs<RouteConfig>(opts: { configs: RouteConfig })`
|
|
41
189
|
|
|
42
|
-
|
|
190
|
+
Like `getRouteConfigs` but additionally merges an HTML response schema for JSX/server-rendered routes.
|
|
43
191
|
|
|
44
|
-
|
|
192
|
+
#### `buildRouteMiddlewares<RouteConfig>(opts: { configs: RouteConfig })`
|
|
45
193
|
|
|
46
|
-
|
|
194
|
+
Internal method that extracts `authenticate`, `authorize`, and `middleware` from a route config and builds the middleware chain:
|
|
47
195
|
|
|
48
|
-
|
|
196
|
+
1. If `authenticate.strategies` is non-empty, adds `authenticateFn` middleware
|
|
197
|
+
2. If `authorize` is present (single spec or array), adds `authorizeFn` middleware(s)
|
|
198
|
+
3. If `middleware` is present (single or array), appends custom middleware(s)
|
|
49
199
|
|
|
50
|
-
|
|
200
|
+
Returns `{ restConfig, security, mws }`.
|
|
51
201
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
202
|
+
#### `binding(): ValueOrPromise<void>` (abstract)
|
|
203
|
+
|
|
204
|
+
Override to register routes manually using `bindRoute` or `defineRoute`.
|
|
205
|
+
|
|
206
|
+
## `BaseRestController`
|
|
207
|
+
|
|
208
|
+
Extends `AbstractRestController` with concrete implementations for `bindRoute`, `defineRoute`, and `defineJSXRoute`.
|
|
209
|
+
|
|
210
|
+
### `defineRoute<RouteConfig, ResponseType>(opts)`
|
|
211
|
+
|
|
212
|
+
Defines and registers a route with its handler in a single call.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
defineRoute<RouteConfig extends IAuthRouteConfig, ResponseType = unknown>(opts: {
|
|
216
|
+
configs: RouteConfig;
|
|
217
|
+
handler: TRouteHandler<ResponseType, RouteEnv>;
|
|
218
|
+
hook?: Hook<any, RouteEnv, string, ValueOrPromise<any>>;
|
|
219
|
+
}): IDefineRouteOptions<RouteConfig, RouteEnv, RouteSchema, BasePath>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
- **`configs`**: Route configuration including path, method, request/response schemas, and optional auth
|
|
223
|
+
- **`handler`**: Route handler function `(context: TRouteContext) => Response`
|
|
224
|
+
- **`hook`**: Optional Hono hook for validation error handling
|
|
225
|
+
|
|
226
|
+
Returns `{ configs, route }`.
|
|
227
|
+
|
|
228
|
+
### `bindRoute<RouteConfig>(opts)`
|
|
229
|
+
|
|
230
|
+
Creates a fluent binding for two-step route registration.
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
bindRoute<RouteConfig extends IAuthRouteConfig>(opts: {
|
|
234
|
+
configs: RouteConfig;
|
|
235
|
+
}): IBindRouteOptions<RouteConfig, RouteEnv, RouteSchema, BasePath>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Returns `{ configs, to }` where `to({ handler })` completes the registration and returns `{ configs, route }`.
|
|
239
|
+
|
|
240
|
+
### `defineJSXRoute<RouteConfig, ResponseType>(opts)`
|
|
241
|
+
|
|
242
|
+
Defines a route that renders server-side HTML via `c.html()`. Same signature as `defineRoute` but uses `getJSXRouteConfigs` instead of `getRouteConfigs`, which automatically adds an HTML response schema.
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
defineJSXRoute<RouteConfig extends IAuthRouteConfig, ResponseType = unknown>(opts: {
|
|
246
|
+
configs: RouteConfig;
|
|
247
|
+
handler: TRouteHandler<ResponseType, RouteEnv>;
|
|
248
|
+
hook?: Hook<any, RouteEnv, string, ValueOrPromise<any>>;
|
|
249
|
+
}): IDefineRouteOptions<RouteConfig, RouteEnv, RouteSchema, BasePath>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### `toHonoHandler<ResponseType>(opts: { handler })`
|
|
253
|
+
|
|
254
|
+
Casts a `TRouteHandler` to Hono's OpenAPI handler type.
|
|
255
|
+
|
|
256
|
+
## Key Types
|
|
257
|
+
|
|
258
|
+
### `IAuthRouteConfig`
|
|
259
|
+
|
|
260
|
+
Route configuration extended with optional authentication and authorization fields. Extends Hono's `RouteConfig`.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
interface IAuthRouteConfig extends HonoRouteConfig {
|
|
264
|
+
authenticate?: { strategies?: TAuthStrategy[]; mode?: TAuthMode };
|
|
265
|
+
authorize?: IAuthorizationSpec | IAuthorizationSpec[];
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### `IControllerOptions`
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
interface IControllerOptions {
|
|
273
|
+
scope: string;
|
|
274
|
+
path?: string; // Falls back to @controller decorator path
|
|
275
|
+
isStrict?: boolean; // Default: true
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### `TRouteContext`
|
|
280
|
+
|
|
281
|
+
Lightweight typed context that provides type-safe `req.valid()` calls:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
type TRouteContext<RouteEnv extends Env = Env> = TContext<RouteEnv, keyof IValidRequestProps>;
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Where `IValidRequestProps` supports: `json`, `query`, `param`, `header`, `cookie`, `form`.
|
|
288
|
+
|
|
289
|
+
### `TRouteHandler`
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
type TRouteHandler<ResponseType = unknown, RouteEnv extends Env = Env> = (
|
|
293
|
+
context: TRouteContext<RouteEnv>,
|
|
294
|
+
) => ValueOrPromise<Response | TypedResponse<ResponseType>>;
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### `IBindRouteOptions`
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
interface IBindRouteOptions<RouteConfig, RouteEnv, RouteSchema, BasePath> {
|
|
301
|
+
configs: RouteConfig;
|
|
302
|
+
to: <ResponseType = unknown>(opts: {
|
|
303
|
+
handler: TRouteHandler<ResponseType, RouteEnv>;
|
|
304
|
+
}) => IDefineRouteOptions<RouteConfig, RouteEnv, RouteSchema, BasePath>;
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### `IDefineRouteOptions`
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
interface IDefineRouteOptions<RouteConfig, RouteEnv, RouteSchema, BasePath> {
|
|
312
|
+
configs: ReturnType<typeof createRoute<string, RouteConfig>>;
|
|
313
|
+
route: OpenAPIHono<RouteEnv, RouteSchema, BasePath>;
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### `IController`
|
|
318
|
+
|
|
319
|
+
Base controller interface:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
interface IController<RouteEnv, RouteSchema, BasePath, ConfigurableOptions>
|
|
323
|
+
extends IConfigurable<ConfigurableOptions, OpenAPIHono<RouteEnv, RouteSchema, BasePath>> {
|
|
324
|
+
router: OpenAPIHono<RouteEnv, RouteSchema, BasePath>;
|
|
325
|
+
bindRoute<RouteConfig>(opts: { configs: RouteConfig }): IBindRouteOptions<...>;
|
|
326
|
+
defineRoute<RouteConfig, ResponseType>(opts: { configs; handler; hook? }): IDefineRouteOptions<...>;
|
|
327
|
+
}
|
|
328
|
+
```
|
|
55
329
|
|
|
56
|
-
|
|
330
|
+
### `asTypedContext`
|
|
57
331
|
|
|
58
|
-
|
|
332
|
+
Utility to cast middleware context to `TContext`:
|
|
59
333
|
|
|
60
334
|
```typescript
|
|
61
|
-
|
|
335
|
+
const asTypedContext = <E extends Env>(context: unknown): TContext<E, string> => {
|
|
336
|
+
return context as TContext<E, string>;
|
|
337
|
+
};
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Route Auth Types (for CRUD Controllers)
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
// Per-route authentication config
|
|
344
|
+
type TRouteAuthenticateConfig =
|
|
345
|
+
| { skip: true }
|
|
346
|
+
| { skip?: false; strategies?: TAuthStrategy[]; mode?: TAuthMode };
|
|
347
|
+
|
|
348
|
+
// Per-route authorization config
|
|
349
|
+
type TRouteAuthorizeConfig = { skip: true } | IAuthorizationSpec | IAuthorizationSpec[];
|
|
350
|
+
|
|
351
|
+
// Combined per-route auth config
|
|
352
|
+
type TRouteAuthConfig = {
|
|
353
|
+
authenticate?: TRouteAuthenticateConfig;
|
|
354
|
+
authorize?: TRouteAuthorizeConfig;
|
|
355
|
+
};
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### `TCustomizableRouteConfig`
|
|
359
|
+
|
|
360
|
+
Per-route customization for CRUD controller endpoints:
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
type TCustomizableRouteConfig = TRouteAuthConfig & {
|
|
364
|
+
request?: {
|
|
365
|
+
params?: TAnyObjectSchema;
|
|
366
|
+
query?: TAnyObjectSchema;
|
|
367
|
+
body?: TAnyObjectSchema;
|
|
368
|
+
headers?: TAnyObjectSchema;
|
|
369
|
+
};
|
|
370
|
+
response?: {
|
|
371
|
+
schema?: z.ZodTypeAny;
|
|
372
|
+
headers?: TResponseHeaders;
|
|
373
|
+
};
|
|
374
|
+
};
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### `ICustomizableRoutes`
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
interface ICustomizableRoutes<
|
|
381
|
+
RouteConfig extends TCustomizableRouteConfig = TCustomizableRouteConfig,
|
|
382
|
+
> {
|
|
383
|
+
count?: RouteConfig;
|
|
384
|
+
find?: RouteConfig;
|
|
385
|
+
findById?: RouteConfig;
|
|
386
|
+
findOne?: RouteConfig;
|
|
387
|
+
create?: RouteConfig;
|
|
388
|
+
updateById?: RouteConfig;
|
|
389
|
+
updateBy?: RouteConfig;
|
|
390
|
+
deleteById?: RouteConfig;
|
|
391
|
+
deleteBy?: RouteConfig;
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Route Decorators
|
|
396
|
+
|
|
397
|
+
**File:** `packages/core/src/base/metadata/routes/rest.ts`
|
|
398
|
+
|
|
399
|
+
### `@controller` Decorator
|
|
400
|
+
|
|
401
|
+
Registers controller metadata (path, transport, tags, description) via the `MetadataRegistry`.
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
import { controller } from '@venizia/ignis';
|
|
405
|
+
|
|
406
|
+
@controller({ path: '/users' })
|
|
407
|
+
export class UserController extends BaseRestController { ... }
|
|
408
|
+
|
|
409
|
+
// With additional metadata
|
|
410
|
+
@controller({ path: '/users', tags: ['Users'], description: 'User management' })
|
|
411
|
+
export class UserController extends BaseRestController { ... }
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### `@api` Decorator
|
|
415
|
+
|
|
416
|
+
Generic route decorator. Registers route config in the metadata registry.
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
import { api, BaseRestController, controller, jsonResponse, z, TRouteContext } from '@venizia/ignis';
|
|
62
420
|
import { HTTP } from '@venizia/ignis-helpers';
|
|
63
421
|
|
|
64
422
|
const MyRouteConfig = {
|
|
@@ -68,7 +426,7 @@ const MyRouteConfig = {
|
|
|
68
426
|
} as const;
|
|
69
427
|
|
|
70
428
|
@controller({ path: '/my-feature' })
|
|
71
|
-
export class MyFeatureController extends
|
|
429
|
+
export class MyFeatureController extends BaseRestController {
|
|
72
430
|
|
|
73
431
|
@api({ configs: MyRouteConfig })
|
|
74
432
|
getData(c: TRouteContext) {
|
|
@@ -77,15 +435,15 @@ export class MyFeatureController extends BaseController {
|
|
|
77
435
|
}
|
|
78
436
|
```
|
|
79
437
|
|
|
80
|
-
|
|
438
|
+
### HTTP Method Decorators (`@get`, `@post`, `@put`, `@patch`, `@del`)
|
|
81
439
|
|
|
82
|
-
|
|
440
|
+
Shorthand decorators that auto-set the HTTP method. Accept the same `configs` object as `@api` but without the `method` property.
|
|
83
441
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
442
|
+
```typescript
|
|
443
|
+
import { get, post, put, patch, del } from '@venizia/ignis';
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Each decorator calls `@api` internally with the appropriate `HTTP.Methods.*` value.
|
|
89
447
|
|
|
90
448
|
**Example using `@get` and `@post`:**
|
|
91
449
|
|
|
@@ -93,19 +451,16 @@ For convenience, `Ignis` provides decorator shortcuts for each HTTP method: Thes
|
|
|
93
451
|
import { get, post, z, jsonContent, jsonResponse, Authentication, TRouteContext } from '@venizia/ignis';
|
|
94
452
|
import { HTTP } from '@venizia/ignis-helpers';
|
|
95
453
|
|
|
96
|
-
// Define route configs as const
|
|
97
454
|
const UserRoutes = {
|
|
98
455
|
LIST_USERS: {
|
|
99
456
|
path: '/',
|
|
100
|
-
method: 'get',
|
|
101
457
|
responses: jsonResponse({
|
|
102
458
|
description: 'A list of users',
|
|
103
459
|
schema: z.array(z.object({ id: z.string(), name: z.string() })),
|
|
104
460
|
}),
|
|
105
461
|
},
|
|
106
462
|
GET_USER: {
|
|
107
|
-
path: '
|
|
108
|
-
method: 'get',
|
|
463
|
+
path: '/{id}',
|
|
109
464
|
request: {
|
|
110
465
|
params: z.object({ id: z.string() }),
|
|
111
466
|
},
|
|
@@ -116,8 +471,7 @@ const UserRoutes = {
|
|
|
116
471
|
},
|
|
117
472
|
CREATE_USER: {
|
|
118
473
|
path: '/',
|
|
119
|
-
|
|
120
|
-
authStrategies: [Authentication.STRATEGY_JWT], // Secure this endpoint
|
|
474
|
+
authenticate: { strategies: [Authentication.STRATEGY_JWT] },
|
|
121
475
|
request: {
|
|
122
476
|
body: jsonContent({
|
|
123
477
|
schema: z.object({ name: z.string() }),
|
|
@@ -138,75 +492,77 @@ const UserRoutes = {
|
|
|
138
492
|
|
|
139
493
|
@get({ configs: UserRoutes.GET_USER })
|
|
140
494
|
getUserById(c: TRouteContext) {
|
|
141
|
-
const { id } = c.req.valid<{ id: string }>('param');
|
|
495
|
+
const { id } = c.req.valid<{ id: string }>('param');
|
|
142
496
|
return c.json({ id, name: 'John Doe' }, HTTP.ResultCodes.RS_2.Ok);
|
|
143
497
|
}
|
|
144
498
|
|
|
145
499
|
@post({ configs: UserRoutes.CREATE_USER })
|
|
146
500
|
createUser(c: TRouteContext) {
|
|
147
|
-
const { name } = c.req.valid<{ name: string }>('json');
|
|
501
|
+
const { name } = c.req.valid<{ name: string }>('json');
|
|
148
502
|
const newUser = { id: '2', name };
|
|
149
503
|
return c.json(newUser, HTTP.ResultCodes.RS_2.Created);
|
|
150
504
|
}
|
|
151
505
|
```
|
|
152
506
|
|
|
153
|
-
|
|
507
|
+
### Decorator-Based Routing Notes
|
|
154
508
|
|
|
155
|
-
|
|
509
|
+
- The `binding()` method is not required if you use only decorator-based routing
|
|
510
|
+
- Routes are discovered and registered during `configure()` via `registerRoutesFromRegistry()`
|
|
511
|
+
- TypeScript automatically infers and validates return types against the OpenAPI response schema — no need for explicit `TRouteResponse` annotations
|
|
512
|
+
- Use `as const` on route config objects for strict type inference
|
|
156
513
|
|
|
157
|
-
|
|
158
|
-
import { api, BaseController, controller, TRouteContext, jsonContent, jsonResponse } from '@venizia/ignis';
|
|
159
|
-
import { HTTP } from '@venizia/ignis-helpers';
|
|
160
|
-
import { z } from 'hono/zod-openapi';
|
|
514
|
+
## Manual Route Definition
|
|
161
515
|
|
|
162
|
-
|
|
163
|
-
PING: {
|
|
164
|
-
method: HTTP.Methods.POST,
|
|
165
|
-
path: '/ping',
|
|
166
|
-
request: {
|
|
167
|
-
body: jsonContent({
|
|
168
|
-
schema: z.object({ message: z.string().min(1) }),
|
|
169
|
-
}),
|
|
170
|
-
},
|
|
171
|
-
responses: jsonResponse({
|
|
172
|
-
schema: z.object({ pong: z.string() }),
|
|
173
|
-
}),
|
|
174
|
-
},
|
|
175
|
-
} as const; // Use 'as const' for strict type inference
|
|
516
|
+
For advanced use cases — dynamic routes, feature flags, programmatic control — define routes inside `binding()`.
|
|
176
517
|
|
|
177
|
-
|
|
178
|
-
export class HealthCheckController extends BaseController {
|
|
518
|
+
### `defineRoute` Example
|
|
179
519
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
520
|
+
```typescript
|
|
521
|
+
this.defineRoute({
|
|
522
|
+
configs: {
|
|
523
|
+
method: 'get',
|
|
524
|
+
path: '/status',
|
|
525
|
+
responses: jsonResponse({ schema: z.object({ ok: z.boolean() }) }),
|
|
526
|
+
authenticate: { strategies: ['jwt'] },
|
|
527
|
+
authorize: { resource: 'status', scopes: ['read'] },
|
|
528
|
+
},
|
|
529
|
+
handler: async (context) => {
|
|
530
|
+
return context.json({ ok: true }, 200);
|
|
531
|
+
},
|
|
532
|
+
hook: (result, context) => {
|
|
533
|
+
// Optional hook for post-processing
|
|
534
|
+
},
|
|
535
|
+
});
|
|
186
536
|
```
|
|
187
537
|
|
|
188
|
-
###
|
|
189
|
-
|
|
190
|
-
For advanced use cases or when you prefer a non-decorator approach, you can define routes manually using `defineRoute` and `bindRoute` methods inside the `binding()` method.
|
|
538
|
+
### `bindRoute` Example
|
|
191
539
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
540
|
+
```typescript
|
|
541
|
+
const { configs } = this.bindRoute({
|
|
542
|
+
configs: {
|
|
543
|
+
method: 'post',
|
|
544
|
+
path: '/action',
|
|
545
|
+
request: {
|
|
546
|
+
body: jsonContent({ schema: z.object({ name: z.string() }) }),
|
|
547
|
+
},
|
|
548
|
+
responses: jsonResponse({ schema: z.object({ id: z.string() }) }),
|
|
549
|
+
},
|
|
550
|
+
}).to({
|
|
551
|
+
handler: async (context) => {
|
|
552
|
+
const data = context.req.valid('json');
|
|
553
|
+
return context.json({ id: '123' }, 201);
|
|
554
|
+
},
|
|
555
|
+
});
|
|
556
|
+
```
|
|
201
557
|
|
|
202
|
-
|
|
558
|
+
### `defineJSXRoute` Example
|
|
203
559
|
|
|
204
560
|
```typescript
|
|
205
561
|
this.defineJSXRoute({
|
|
206
562
|
configs: {
|
|
207
563
|
path: '/dashboard',
|
|
208
564
|
method: 'get',
|
|
209
|
-
responses:
|
|
565
|
+
responses: {}, // HTML response schema is auto-merged
|
|
210
566
|
},
|
|
211
567
|
handler: async (c) => {
|
|
212
568
|
const data = await this.dashboardService.getData();
|
|
@@ -218,53 +574,22 @@ this.defineJSXRoute({
|
|
|
218
574
|
});
|
|
219
575
|
```
|
|
220
576
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
#### `defineRoute`
|
|
224
|
-
|
|
225
|
-
This method is for creating API endpoints. It now handles both public and authenticated routes by accepting an `authStrategies` array within the `configs`.
|
|
226
|
-
|
|
227
|
-
```typescript
|
|
228
|
-
this.defineRoute({
|
|
229
|
-
configs: TAuthRouteConfig<RouteConfig>; // You would define this inline or via a const
|
|
230
|
-
handler: TLazyRouteHandler<typeof configs, RouteEnv>; // Inferred from configs
|
|
231
|
-
hook?: Hook;
|
|
232
|
-
});
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
- **`configs`**: An object that defines the route's OpenAPI specification. It now includes an optional `authStrategies` array. See the table below for details.
|
|
236
|
-
- **`handler`**: The Hono route handler function `(c: Context) => Response`.
|
|
237
|
-
- **`hook`**: An optional hook for processing the request or response, often used for validation error handling.
|
|
577
|
+
## `IAuthRouteConfig` Options
|
|
238
578
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
This method offers a fluent API for defining routes, similar to `defineRoute`, but structured for chaining. It also supports `authStrategies`.
|
|
242
|
-
|
|
243
|
-
```typescript
|
|
244
|
-
this.bindRoute({
|
|
245
|
-
configs: TAuthRouteConfig<RouteConfig>; // You would define this inline or via a const
|
|
246
|
-
}).to({
|
|
247
|
-
handler: TLazyRouteHandler<typeof configs, RouteEnv>; // Inferred from configs
|
|
248
|
-
});
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
- **`configs`**: Same as `defineRoute`, including `authStrategies`.
|
|
252
|
-
- **`to`**: A method that accepts an object with the `handler` function.
|
|
253
|
-
|
|
254
|
-
### `TRouteConfig` Options
|
|
255
|
-
|
|
256
|
-
The `configs` object accepts properties based on the OpenAPI 3.0 specification.
|
|
579
|
+
The `configs` object extends the OpenAPI 3.0 `RouteConfig` from `@hono/zod-openapi`.
|
|
257
580
|
|
|
258
581
|
| Property | Type | Description |
|
|
259
582
|
| :--- | :--- | :--- |
|
|
260
|
-
| `path` | `string` |
|
|
261
|
-
| `method` | `'get' \| 'post' \|
|
|
262
|
-
| `request` | `object` |
|
|
263
|
-
| `responses
|
|
264
|
-
| `tags` | `string[]` |
|
|
265
|
-
| `summary` | `string` |
|
|
266
|
-
| `description
|
|
267
|
-
| `
|
|
583
|
+
| `path` | `string` | Route path relative to the controller's base path (e.g., `/{id}`) |
|
|
584
|
+
| `method` | `'get' \| 'post' \| 'put' \| 'patch' \| 'delete'` | HTTP method (auto-set by `@get`, `@post`, etc.) |
|
|
585
|
+
| `request` | `object` | Request definition: `params`, `query`, `body`, `headers` (Zod schemas) |
|
|
586
|
+
| `responses` | `object` | HTTP status code to response description/schema mapping |
|
|
587
|
+
| `tags` | `string[]` | OpenAPI tags. The controller's `scope` is automatically appended |
|
|
588
|
+
| `summary` | `string` | Short summary of the operation |
|
|
589
|
+
| `description` | `string` | Detailed description of the operation |
|
|
590
|
+
| `authenticate` | `{ strategies?: TAuthStrategy[]; mode?: TAuthMode }` | Auth strategies. If provided, framework injects auth middleware automatically |
|
|
591
|
+
| `authorize` | `IAuthorizationSpec \| IAuthorizationSpec[]` | Authorization spec(s). If provided, framework injects authorize middleware after authenticate |
|
|
592
|
+
| `middleware` | `MiddlewareHandler \| MiddlewareHandler[]` | Custom middleware(s) appended after auth middleware |
|
|
268
593
|
|
|
269
594
|
### Example of `request` Configuration
|
|
270
595
|
|
|
@@ -283,101 +608,87 @@ request: {
|
|
|
283
608
|
// ...
|
|
284
609
|
```
|
|
285
610
|
|
|
286
|
-
|
|
611
|
+
## Standard Headers and Constants
|
|
287
612
|
|
|
288
|
-
|
|
613
|
+
**File:** `packages/core/src/base/controllers/common/constants.ts`
|
|
289
614
|
|
|
290
|
-
|
|
615
|
+
### `RestPaths`
|
|
291
616
|
|
|
292
617
|
```typescript
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
method: HTTP.Methods.GET,
|
|
299
|
-
path: '/',
|
|
300
|
-
responses: jsonResponse({
|
|
301
|
-
schema: z.object({ status: z.string() }),
|
|
302
|
-
}),
|
|
303
|
-
},
|
|
304
|
-
PING: {
|
|
305
|
-
method: HTTP.Methods.POST,
|
|
306
|
-
path: '/ping',
|
|
307
|
-
request: {
|
|
308
|
-
body: jsonContent({
|
|
309
|
-
schema: z.object({ message: z.string() }),
|
|
310
|
-
}),
|
|
311
|
-
},
|
|
312
|
-
responses: jsonResponse({
|
|
313
|
-
schema: z.object({ message: z.string() }),
|
|
314
|
-
}),
|
|
315
|
-
},
|
|
316
|
-
});
|
|
618
|
+
class RestPaths {
|
|
619
|
+
static readonly ROOT = '/';
|
|
620
|
+
static readonly COUNT = '/count';
|
|
621
|
+
static readonly FIND_ONE = '/find-one';
|
|
622
|
+
}
|
|
317
623
|
```
|
|
318
624
|
|
|
625
|
+
### Built-in Header Schemas
|
|
626
|
+
|
|
627
|
+
| Constant | Headers Included |
|
|
628
|
+
| :--- | :--- |
|
|
629
|
+
| `trackableHeaders` | `x-request-id`, `x-request-channel`, `x-request-device-info` (all optional) |
|
|
630
|
+
| `countableHeaders` | `x-request-count` — controls `{count, data}` vs data-only response format |
|
|
631
|
+
| `defaultRequestHeaders` | `trackableHeaders` + `countableHeaders` combined |
|
|
632
|
+
| `commonResponseHeaders` | `x-request-id` (echo), `x-response-count`, `x-response-format` |
|
|
633
|
+
| `findResponseHeaders` | `commonResponseHeaders` + `content-range` for pagination |
|
|
634
|
+
|
|
319
635
|
## `ControllerFactory`
|
|
320
636
|
|
|
321
|
-
The `ControllerFactory` provides a static method `defineCrudController` to quickly generate a pre-configured CRUD controller for any given `BaseEntity` and its corresponding repository.
|
|
637
|
+
The `ControllerFactory` provides a static method `defineCrudController` to quickly generate a pre-configured CRUD controller for any given `BaseEntity` and its corresponding repository.
|
|
322
638
|
|
|
323
|
-
|
|
639
|
+
**File:** `packages/core/src/base/controllers/factory/controller.ts`
|
|
324
640
|
|
|
325
641
|
### `static defineCrudController<EntitySchema>(opts: ICrudControllerOptions<EntitySchema>)`
|
|
326
642
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
**Note:** The returned class is dynamically named using `controller.name` from the options. This ensures that when registered with `app.controller()`, the class has a proper name for binding keys and debugging (e.g., `ConfigurationController` instead of an anonymous class).
|
|
643
|
+
Returns a `BaseRestController` subclass with standard CRUD endpoints pre-configured. The returned class is dynamically named using `controller.name` from the options.
|
|
330
644
|
|
|
331
645
|
| Route Name | Method | Path | Description |
|
|
332
646
|
| :--- | :--- | :--- | :--- |
|
|
333
|
-
| `count` | `GET` | `/count` |
|
|
334
|
-
| `find` | `GET` | `/` |
|
|
335
|
-
| `findById` | `GET` |
|
|
336
|
-
| `findOne` | `GET` | `/find-one` |
|
|
337
|
-
| `create` | `POST` | `/` | Create a new record
|
|
338
|
-
| `updateById` | `PATCH` |
|
|
339
|
-
| `updateBy` | `PATCH` | `/` |
|
|
340
|
-
| `deleteById` | `DELETE` |
|
|
341
|
-
| `deleteBy` | `DELETE` | `/` |
|
|
647
|
+
| `count` | `GET` | `/count` | Count records matching a where condition |
|
|
648
|
+
| `find` | `GET` | `/` | Find records with filter, pagination, sorting, and relations |
|
|
649
|
+
| `findById` | `GET` | `/{id}` | Find a single record by its ID |
|
|
650
|
+
| `findOne` | `GET` | `/find-one` | Find the first record matching a filter |
|
|
651
|
+
| `create` | `POST` | `/` | Create a new record |
|
|
652
|
+
| `updateById` | `PATCH` | `/{id}` | Partial update a record by its ID |
|
|
653
|
+
| `updateBy` | `PATCH` | `/` | Bulk update records matching a `where` filter |
|
|
654
|
+
| `deleteById` | `DELETE` | `/{id}` | Delete a record by its ID |
|
|
655
|
+
| `deleteBy` | `DELETE` | `/` | Bulk delete records matching a `where` filter |
|
|
342
656
|
|
|
343
657
|
### `ICrudControllerOptions<EntitySchema>`
|
|
344
658
|
|
|
345
659
|
| Option | Type | Description |
|
|
346
660
|
| :--- | :--- | :--- |
|
|
347
|
-
| `entity` | `TClass<BaseEntity<EntitySchema>> \| TResolver<TClass<BaseEntity<EntitySchema>>>` |
|
|
348
|
-
| `repository.name` | `string` |
|
|
349
|
-
| `controller.name` | `string` |
|
|
350
|
-
| `controller.basePath
|
|
351
|
-
| `controller.readonly` | `boolean` | If `true`, only read operations (find, findOne, findById
|
|
352
|
-
| `controller.isStrict` | `boolean` |
|
|
353
|
-
| `
|
|
354
|
-
| `
|
|
355
|
-
| `routes` | `
|
|
661
|
+
| `entity` | `TClass<BaseEntity<EntitySchema>> \| TResolver<TClass<BaseEntity<EntitySchema>>>` | Entity class or resolver function returning it. Used to derive request/response schemas |
|
|
662
|
+
| `repository.name` | `string` | Repository binding key name in the IoC container (e.g., `'ConfigurationRepository'`) |
|
|
663
|
+
| `controller.name` | `string` | Unique name for the generated controller (e.g., `'ConfigurationController'`) |
|
|
664
|
+
| `controller.basePath` | `string` | Base path for all routes (e.g., `'/configurations'`). Required |
|
|
665
|
+
| `controller.readonly` | `boolean` | If `true`, only read operations (count, find, findOne, findById) are generated. Defaults to `false` |
|
|
666
|
+
| `controller.isStrict` | `{ path?: boolean; requestSchema?: boolean }` | `path` (default `true`): strict path matching. `requestSchema` (default `true`): strict query parameter validation |
|
|
667
|
+
| `authenticate` | `{ strategies?: TAuthStrategy[]; mode?: TAuthMode }` | Authentication config applied to all routes (unless overridden per-route) |
|
|
668
|
+
| `authorize` | `IAuthorizationSpec \| IAuthorizationSpec[]` | Authorization config applied to all routes (unless overridden per-route) |
|
|
669
|
+
| `routes` | `ICustomizableRoutes` | Per-route configuration combining schema and auth overrides |
|
|
356
670
|
|
|
357
671
|
### Routes Configuration
|
|
358
672
|
|
|
359
|
-
The `routes` option provides
|
|
673
|
+
The `routes` option provides per-route customization of request/response schemas and auth:
|
|
360
674
|
|
|
361
675
|
```typescript
|
|
362
|
-
type TRouteAuthConfig =
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
type TRequestConfig = {
|
|
367
|
-
query?: z.ZodObject; // Custom query parameters
|
|
368
|
-
headers?: z.ZodObject; // Custom headers
|
|
369
|
-
params?: z.ZodObject; // Custom path parameters
|
|
370
|
-
body?: z.ZodObject; // Custom request body (write routes only)
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
type TResponseConfig = {
|
|
374
|
-
schema?: z.ZodObject; // Custom response body schema
|
|
375
|
-
headers?: z.ZodObject; // Custom response headers
|
|
676
|
+
type TRouteAuthConfig = {
|
|
677
|
+
authenticate?: { skip: true } | { skip?: false; strategies?: TAuthStrategy[]; mode?: TAuthMode };
|
|
678
|
+
authorize?: { skip: true } | IAuthorizationSpec | IAuthorizationSpec[];
|
|
376
679
|
};
|
|
377
680
|
|
|
378
|
-
type
|
|
379
|
-
request?:
|
|
380
|
-
|
|
681
|
+
type TCustomizableRouteConfig = TRouteAuthConfig & {
|
|
682
|
+
request?: {
|
|
683
|
+
params?: TAnyObjectSchema;
|
|
684
|
+
query?: TAnyObjectSchema;
|
|
685
|
+
body?: TAnyObjectSchema;
|
|
686
|
+
headers?: TAnyObjectSchema;
|
|
687
|
+
};
|
|
688
|
+
response?: {
|
|
689
|
+
schema?: z.ZodTypeAny;
|
|
690
|
+
headers?: TResponseHeaders;
|
|
691
|
+
};
|
|
381
692
|
};
|
|
382
693
|
```
|
|
383
694
|
|
|
@@ -395,11 +706,18 @@ type TBaseRouteConfig = TRouteAuthConfig & {
|
|
|
395
706
|
|
|
396
707
|
### Auth Resolution Priority
|
|
397
708
|
|
|
398
|
-
When resolving authentication for a route
|
|
709
|
+
When resolving authentication for a route:
|
|
710
|
+
|
|
711
|
+
1. **Endpoint `authenticate: { skip: true }`** — No auth (ignores controller `authenticate`)
|
|
712
|
+
2. **Endpoint `authenticate: { strategies }`** — Override controller (empty array = no auth)
|
|
713
|
+
3. **Controller `authenticate`** — Default fallback
|
|
399
714
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
715
|
+
When resolving authorization for a route:
|
|
716
|
+
|
|
717
|
+
1. **Endpoint `authenticate: { skip: true }`** — No authorize (auth skipped entirely)
|
|
718
|
+
2. **Endpoint `authorize: { skip: true }`** — No authorize (explicitly skipped)
|
|
719
|
+
3. **Endpoint `authorize: { ... }`** — Override controller authorize
|
|
720
|
+
4. **Controller `authorize`** — Default fallback
|
|
403
721
|
|
|
404
722
|
### Authentication Examples
|
|
405
723
|
|
|
@@ -411,7 +729,7 @@ const UserController = ControllerFactory.defineCrudController({
|
|
|
411
729
|
entity: UserEntity,
|
|
412
730
|
repository: { name: 'UserRepository' },
|
|
413
731
|
controller: { name: 'UserController', basePath: '/users' },
|
|
414
|
-
|
|
732
|
+
authenticate: { strategies: [Authentication.STRATEGY_JWT] },
|
|
415
733
|
});
|
|
416
734
|
|
|
417
735
|
// 2. JWT auth on all, but skip for public read endpoints
|
|
@@ -419,11 +737,11 @@ const ProductController = ControllerFactory.defineCrudController({
|
|
|
419
737
|
entity: ProductEntity,
|
|
420
738
|
repository: { name: 'ProductRepository' },
|
|
421
739
|
controller: { name: 'ProductController', basePath: '/products' },
|
|
422
|
-
|
|
740
|
+
authenticate: { strategies: [Authentication.STRATEGY_JWT] },
|
|
423
741
|
routes: {
|
|
424
|
-
find: {
|
|
425
|
-
findById: {
|
|
426
|
-
count: {
|
|
742
|
+
find: { authenticate: { skip: true } },
|
|
743
|
+
findById: { authenticate: { skip: true } },
|
|
744
|
+
count: { authenticate: { skip: true } },
|
|
427
745
|
},
|
|
428
746
|
});
|
|
429
747
|
|
|
@@ -433,24 +751,26 @@ const ArticleController = ControllerFactory.defineCrudController({
|
|
|
433
751
|
repository: { name: 'ArticleRepository' },
|
|
434
752
|
controller: { name: 'ArticleController', basePath: '/articles' },
|
|
435
753
|
routes: {
|
|
436
|
-
create: {
|
|
437
|
-
updateById: {
|
|
438
|
-
deleteById: {
|
|
754
|
+
create: { authenticate: { strategies: [Authentication.STRATEGY_JWT] } },
|
|
755
|
+
updateById: { authenticate: { strategies: [Authentication.STRATEGY_JWT] } },
|
|
756
|
+
deleteById: { authenticate: { strategies: [Authentication.STRATEGY_JWT] } },
|
|
439
757
|
},
|
|
440
758
|
});
|
|
441
759
|
|
|
442
|
-
// 4. Custom request/response schemas with auth
|
|
760
|
+
// 4. Custom request/response schemas with auth and authorization
|
|
443
761
|
const OrderController = ControllerFactory.defineCrudController({
|
|
444
762
|
entity: OrderEntity,
|
|
445
763
|
repository: { name: 'OrderRepository' },
|
|
446
764
|
controller: { name: 'OrderController', basePath: '/orders' },
|
|
447
|
-
|
|
765
|
+
authenticate: { strategies: [Authentication.STRATEGY_JWT] },
|
|
766
|
+
authorize: { resource: 'orders', scopes: ['read'] },
|
|
448
767
|
routes: {
|
|
449
768
|
find: {
|
|
450
|
-
|
|
769
|
+
authenticate: { skip: true },
|
|
451
770
|
response: { schema: CustomOrderListSchema },
|
|
452
771
|
},
|
|
453
772
|
create: {
|
|
773
|
+
authorize: { resource: 'orders', scopes: ['write'] },
|
|
454
774
|
request: { body: CustomOrderCreateSchema },
|
|
455
775
|
response: { schema: CustomOrderResponseSchema },
|
|
456
776
|
},
|
|
@@ -464,14 +784,12 @@ const OrderController = ControllerFactory.defineCrudController({
|
|
|
464
784
|
import { Authentication, ControllerFactory } from '@venizia/ignis';
|
|
465
785
|
import { z } from '@hono/zod-openapi';
|
|
466
786
|
|
|
467
|
-
// Custom request body for create
|
|
468
787
|
const CreateUserSchema = z.object({
|
|
469
788
|
name: z.string().min(1).max(100),
|
|
470
789
|
email: z.string().email(),
|
|
471
790
|
role: z.enum(['admin', 'user']).default('user'),
|
|
472
791
|
});
|
|
473
792
|
|
|
474
|
-
// Custom response schema (omit sensitive fields)
|
|
475
793
|
const PublicUserSchema = z.object({
|
|
476
794
|
id: z.string(),
|
|
477
795
|
name: z.string(),
|
|
@@ -483,33 +801,28 @@ const UserController = ControllerFactory.defineCrudController({
|
|
|
483
801
|
entity: UserEntity,
|
|
484
802
|
repository: { name: 'UserRepository' },
|
|
485
803
|
controller: { name: 'UserController', basePath: '/users' },
|
|
486
|
-
|
|
804
|
+
authenticate: { strategies: [Authentication.STRATEGY_JWT] },
|
|
487
805
|
routes: {
|
|
488
|
-
// Public read endpoints
|
|
489
806
|
find: {
|
|
490
|
-
|
|
807
|
+
authenticate: { skip: true },
|
|
491
808
|
response: { schema: z.array(PublicUserSchema) },
|
|
492
809
|
},
|
|
493
810
|
findById: {
|
|
494
|
-
|
|
811
|
+
authenticate: { skip: true },
|
|
495
812
|
response: { schema: PublicUserSchema },
|
|
496
813
|
},
|
|
497
|
-
|
|
498
|
-
// Custom create with custom body and response
|
|
499
814
|
create: {
|
|
500
815
|
request: { body: CreateUserSchema },
|
|
501
816
|
response: { schema: PublicUserSchema },
|
|
502
817
|
},
|
|
503
|
-
|
|
504
|
-
// Delete requires JWT auth (uses default schema)
|
|
505
818
|
deleteById: {
|
|
506
|
-
|
|
819
|
+
authenticate: { strategies: [Authentication.STRATEGY_JWT] },
|
|
507
820
|
},
|
|
508
821
|
},
|
|
509
822
|
});
|
|
510
823
|
```
|
|
511
824
|
|
|
512
|
-
### Example
|
|
825
|
+
### Full Example
|
|
513
826
|
|
|
514
827
|
```typescript
|
|
515
828
|
// src/controllers/configuration.controller.ts
|
|
@@ -525,18 +838,16 @@ import {
|
|
|
525
838
|
|
|
526
839
|
const BASE_PATH = '/configurations';
|
|
527
840
|
|
|
528
|
-
// Define the CRUD controller using the factory
|
|
529
841
|
const _ConfigurationController = ControllerFactory.defineCrudController({
|
|
530
842
|
repository: { name: ConfigurationRepository.name },
|
|
531
843
|
controller: {
|
|
532
844
|
name: 'ConfigurationController',
|
|
533
845
|
basePath: BASE_PATH,
|
|
534
|
-
isStrict: true,
|
|
846
|
+
isStrict: { path: true, requestSchema: true },
|
|
535
847
|
},
|
|
536
|
-
entity: () => Configuration,
|
|
848
|
+
entity: () => Configuration,
|
|
537
849
|
});
|
|
538
850
|
|
|
539
|
-
// Extend the generated controller to add custom logic or inject dependencies
|
|
540
851
|
@controller({ path: BASE_PATH })
|
|
541
852
|
export class ConfigurationController extends _ConfigurationController {
|
|
542
853
|
constructor(
|
|
@@ -548,19 +859,15 @@ export class ConfigurationController extends _ConfigurationController {
|
|
|
548
859
|
})
|
|
549
860
|
repository: ConfigurationRepository,
|
|
550
861
|
) {
|
|
551
|
-
super(repository);
|
|
862
|
+
super(repository);
|
|
552
863
|
}
|
|
553
864
|
}
|
|
554
865
|
```
|
|
555
866
|
|
|
556
|
-
By leveraging these structured configuration options and the `ControllerFactory`, you ensure that your API is not only functional but also well-documented, easy to validate, and rapidly deployable for standard CRUD operations.
|
|
557
|
-
|
|
558
867
|
### Overriding CRUD Methods with Strong Typing
|
|
559
868
|
|
|
560
869
|
When extending a generated CRUD controller, you can override methods using `TRouteContext` and explicit type arguments for validation.
|
|
561
870
|
|
|
562
|
-
#### Example: Full Controller Override Pattern
|
|
563
|
-
|
|
564
871
|
```typescript
|
|
565
872
|
import { Configuration } from '@/models';
|
|
566
873
|
import { ConfigurationRepository } from '@/repositories';
|
|
@@ -577,31 +884,27 @@ import { z } from '@hono/zod-openapi';
|
|
|
577
884
|
|
|
578
885
|
const BASE_PATH = '/configurations';
|
|
579
886
|
|
|
580
|
-
// Custom request body schema
|
|
581
887
|
const CreateConfigurationSchema = z.object({
|
|
582
888
|
code: z.string().min(1).max(100),
|
|
583
889
|
description: z.string().max(500).optional(),
|
|
584
890
|
group: z.string().min(1).max(50),
|
|
585
891
|
});
|
|
586
892
|
|
|
587
|
-
// Infer type for usage
|
|
588
893
|
type TCreateConfiguration = z.infer<typeof CreateConfigurationSchema>;
|
|
589
894
|
|
|
590
|
-
// Custom response schema
|
|
591
895
|
const CreateResponseSchema = z.object({
|
|
592
896
|
id: z.string(),
|
|
593
897
|
code: z.string(),
|
|
594
898
|
message: z.string(),
|
|
595
899
|
});
|
|
596
900
|
|
|
597
|
-
// Define the CRUD controller with custom schemas
|
|
598
901
|
const _Controller = ControllerFactory.defineCrudController({
|
|
599
902
|
repository: { name: ConfigurationRepository.name },
|
|
600
903
|
controller: { name: 'ConfigurationController', basePath: BASE_PATH },
|
|
601
|
-
|
|
904
|
+
authenticate: { strategies: [Authentication.STRATEGY_JWT] },
|
|
602
905
|
entity: () => Configuration,
|
|
603
906
|
routes: {
|
|
604
|
-
count: {
|
|
907
|
+
count: { authenticate: { skip: true } },
|
|
605
908
|
create: {
|
|
606
909
|
request: { body: CreateConfigurationSchema },
|
|
607
910
|
response: { schema: CreateResponseSchema },
|
|
@@ -623,34 +926,25 @@ export class ConfigurationController extends _Controller {
|
|
|
623
926
|
super(repository);
|
|
624
927
|
}
|
|
625
928
|
|
|
626
|
-
// Override with full type safety
|
|
627
929
|
override async create(opts: { context: TRouteContext }) {
|
|
628
930
|
const { context } = opts;
|
|
629
|
-
|
|
630
|
-
// Get typed request body using generic validation
|
|
631
931
|
const data = context.req.valid<TCreateConfiguration>('json');
|
|
632
932
|
|
|
633
|
-
// Access typed properties
|
|
634
933
|
this.logger.info('[create] code: %s, group: %s', data.code, data.group);
|
|
635
934
|
|
|
636
935
|
// Custom business logic here...
|
|
637
|
-
|
|
638
|
-
// Call parent or return custom response
|
|
639
936
|
return super.create(opts);
|
|
640
937
|
}
|
|
641
938
|
|
|
642
|
-
// Override updateById
|
|
643
939
|
override async updateById(opts: { context: TRouteContext }) {
|
|
644
940
|
const { context } = opts;
|
|
645
|
-
// Explicitly type parameters if needed, or rely on schema validation
|
|
646
941
|
const { id } = context.req.valid<{ id: string }>('param');
|
|
647
|
-
|
|
942
|
+
|
|
648
943
|
this.logger.info('[updateById] id: %s', id);
|
|
649
944
|
|
|
650
945
|
return super.updateById(opts);
|
|
651
946
|
}
|
|
652
947
|
|
|
653
|
-
// Override deleteById with audit logging
|
|
654
948
|
override async deleteById(opts: { context: TRouteContext }) {
|
|
655
949
|
const { context } = opts;
|
|
656
950
|
const { id } = context.req.valid<{ id: string }>('param');
|
|
@@ -662,10 +956,19 @@ export class ConfigurationController extends _Controller {
|
|
|
662
956
|
}
|
|
663
957
|
```
|
|
664
958
|
|
|
959
|
+
### Generated Controller Internal Methods
|
|
960
|
+
|
|
961
|
+
The factory-generated controller includes a `normalizeCountData` method that checks the `x-request-count` header to determine response format:
|
|
962
|
+
|
|
963
|
+
- When `x-request-count` is `"true"` or omitted: returns `{ count, data }`
|
|
964
|
+
- When `x-request-count` is `"false"`: returns data only
|
|
965
|
+
|
|
966
|
+
Bulk operations (`updateBy`, `deleteBy`) require a non-empty `where` filter and return `400 Bad Request` if omitted.
|
|
665
967
|
|
|
666
968
|
## See Also
|
|
667
969
|
|
|
668
970
|
- **Related References:**
|
|
971
|
+
- [gRPC Controllers](./grpc-controllers.md) - gRPC controller reference with ConnectRPC integration
|
|
669
972
|
- [Services](./services.md) - Business logic layer called by controllers
|
|
670
973
|
- [Repositories](./repositories/) - Data access layer for CRUD operations
|
|
671
974
|
- [Middlewares](./middlewares.md) - Request/response middleware
|
|
@@ -673,7 +976,7 @@ export class ConfigurationController extends _Controller {
|
|
|
673
976
|
- [Dependency Injection](./dependency-injection.md) - DI patterns and injection
|
|
674
977
|
|
|
675
978
|
- **Guides:**
|
|
676
|
-
- [Controllers Guide](/guides/core-concepts/controllers)
|
|
979
|
+
- [Controllers Guide](/guides/core-concepts/rest-controllers)
|
|
677
980
|
- [Building a CRUD API](/guides/tutorials/building-a-crud-api)
|
|
678
981
|
|
|
679
982
|
- **Best Practices:**
|