@venizia/ignis-docs 0.0.3 → 0.0.4-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 +1 -1
- package/package.json +4 -2
- package/wiki/best-practices/api-usage-examples.md +591 -0
- package/wiki/best-practices/architectural-patterns.md +415 -0
- package/wiki/best-practices/architecture-decisions.md +488 -0
- package/wiki/{get-started/best-practices → best-practices}/code-style-standards.md +406 -17
- package/wiki/{get-started/best-practices → best-practices}/common-pitfalls.md +109 -4
- package/wiki/{get-started/best-practices → best-practices}/contribution-workflow.md +34 -7
- package/wiki/best-practices/data-modeling.md +376 -0
- package/wiki/best-practices/deployment-strategies.md +698 -0
- package/wiki/best-practices/index.md +27 -0
- package/wiki/best-practices/performance-optimization.md +196 -0
- package/wiki/best-practices/security-guidelines.md +218 -0
- package/wiki/{get-started/best-practices → best-practices}/troubleshooting-tips.md +97 -1
- package/wiki/changelogs/2025-12-16-initial-architecture.md +1 -1
- package/wiki/changelogs/2025-12-16-model-repo-datasource-refactor.md +1 -1
- package/wiki/changelogs/2025-12-17-refactor.md +1 -1
- package/wiki/changelogs/2025-12-18-performance-optimizations.md +5 -5
- package/wiki/changelogs/2025-12-18-repository-validation-security.md +13 -7
- package/wiki/changelogs/2025-12-26-nested-relations-and-generics.md +2 -2
- package/wiki/changelogs/2025-12-29-dynamic-binding-registration.md +104 -0
- package/wiki/changelogs/2025-12-29-snowflake-uid-helper.md +100 -0
- package/wiki/changelogs/2025-12-30-repository-enhancements.md +214 -0
- package/wiki/changelogs/2025-12-31-json-path-filtering-array-operators.md +214 -0
- package/wiki/changelogs/2025-12-31-string-id-custom-generator.md +137 -0
- package/wiki/changelogs/2026-01-02-default-filter-and-repository-mixins.md +418 -0
- package/wiki/changelogs/index.md +6 -0
- package/wiki/changelogs/planned-schema-migrator.md +0 -8
- package/wiki/{get-started/core-concepts → guides/core-concepts/application}/bootstrapping.md +18 -5
- package/wiki/{get-started/core-concepts/application.md → guides/core-concepts/application/index.md} +47 -104
- package/wiki/guides/core-concepts/components-guide.md +509 -0
- package/wiki/{get-started → guides}/core-concepts/components.md +24 -17
- package/wiki/{get-started → guides}/core-concepts/controllers.md +30 -13
- package/wiki/{get-started → guides}/core-concepts/dependency-injection.md +97 -0
- package/wiki/guides/core-concepts/persistent/datasources.md +179 -0
- package/wiki/guides/core-concepts/persistent/index.md +119 -0
- package/wiki/guides/core-concepts/persistent/models.md +241 -0
- package/wiki/guides/core-concepts/persistent/repositories.md +219 -0
- package/wiki/guides/core-concepts/persistent/transactions.md +170 -0
- package/wiki/{get-started → guides}/core-concepts/services.md +26 -3
- package/wiki/{get-started → guides/get-started}/5-minute-quickstart.md +59 -14
- package/wiki/guides/get-started/philosophy.md +682 -0
- package/wiki/guides/get-started/setup.md +157 -0
- package/wiki/guides/index.md +89 -0
- package/wiki/guides/reference/glossary.md +243 -0
- package/wiki/{get-started → guides/reference}/mcp-docs-server.md +0 -10
- package/wiki/{get-started → guides/tutorials}/building-a-crud-api.md +134 -132
- package/wiki/{get-started/quickstart.md → guides/tutorials/complete-installation.md} +107 -71
- package/wiki/guides/tutorials/ecommerce-api.md +1399 -0
- package/wiki/guides/tutorials/realtime-chat.md +1261 -0
- package/wiki/guides/tutorials/testing.md +723 -0
- package/wiki/index.md +176 -37
- package/wiki/references/base/application.md +27 -0
- package/wiki/references/base/bootstrapping.md +30 -26
- package/wiki/references/base/components.md +24 -7
- package/wiki/references/base/controllers.md +51 -20
- package/wiki/references/base/datasources.md +30 -0
- package/wiki/references/base/dependency-injection.md +39 -3
- package/wiki/references/base/filter-system/application-usage.md +224 -0
- package/wiki/references/base/filter-system/array-operators.md +132 -0
- package/wiki/references/base/filter-system/comparison-operators.md +109 -0
- package/wiki/references/base/filter-system/default-filter.md +428 -0
- package/wiki/references/base/filter-system/fields-order-pagination.md +155 -0
- package/wiki/references/base/filter-system/index.md +127 -0
- package/wiki/references/base/filter-system/json-filtering.md +197 -0
- package/wiki/references/base/filter-system/list-operators.md +71 -0
- package/wiki/references/base/filter-system/logical-operators.md +156 -0
- package/wiki/references/base/filter-system/null-operators.md +58 -0
- package/wiki/references/base/filter-system/pattern-matching.md +108 -0
- package/wiki/references/base/filter-system/quick-reference.md +431 -0
- package/wiki/references/base/filter-system/range-operators.md +63 -0
- package/wiki/references/base/filter-system/tips.md +190 -0
- package/wiki/references/base/filter-system/use-cases.md +452 -0
- package/wiki/references/base/index.md +90 -0
- package/wiki/references/base/middlewares.md +602 -0
- package/wiki/references/base/models.md +215 -23
- package/wiki/references/base/providers.md +732 -0
- package/wiki/references/base/repositories/advanced.md +555 -0
- package/wiki/references/base/repositories/index.md +228 -0
- package/wiki/references/base/repositories/mixins.md +331 -0
- package/wiki/references/base/repositories/relations.md +486 -0
- package/wiki/references/base/repositories.md +40 -635
- package/wiki/references/base/services.md +28 -4
- package/wiki/references/components/authentication.md +22 -2
- package/wiki/references/components/health-check.md +12 -0
- package/wiki/references/components/index.md +23 -0
- package/wiki/references/components/mail.md +687 -0
- package/wiki/references/components/request-tracker.md +16 -0
- package/wiki/references/components/socket-io.md +18 -0
- package/wiki/references/components/static-asset.md +14 -26
- package/wiki/references/components/swagger.md +17 -0
- package/wiki/references/configuration/environment-variables.md +427 -0
- package/wiki/references/configuration/index.md +73 -0
- package/wiki/references/helpers/cron.md +14 -0
- package/wiki/references/helpers/crypto.md +15 -0
- package/wiki/references/helpers/env.md +16 -0
- package/wiki/references/helpers/error.md +17 -0
- package/wiki/references/helpers/index.md +14 -0
- package/wiki/references/helpers/inversion.md +24 -4
- package/wiki/references/helpers/logger.md +19 -0
- package/wiki/references/helpers/network.md +11 -0
- package/wiki/references/helpers/queue.md +19 -0
- package/wiki/references/helpers/redis.md +21 -0
- package/wiki/references/helpers/socket-io.md +24 -5
- package/wiki/references/helpers/storage.md +18 -10
- package/wiki/references/helpers/testing.md +18 -0
- package/wiki/references/helpers/types.md +16 -0
- package/wiki/references/helpers/uid.md +167 -0
- package/wiki/references/helpers/worker-thread.md +16 -0
- package/wiki/references/index.md +177 -0
- package/wiki/references/quick-reference.md +634 -0
- package/wiki/references/src-details/boot.md +3 -3
- package/wiki/references/src-details/dev-configs.md +0 -4
- package/wiki/references/src-details/docs.md +2 -2
- package/wiki/references/src-details/index.md +86 -0
- package/wiki/references/src-details/inversion.md +1 -6
- package/wiki/references/src-details/mcp-server.md +3 -15
- package/wiki/references/utilities/index.md +86 -10
- package/wiki/references/utilities/jsx.md +577 -0
- package/wiki/references/utilities/request.md +0 -2
- package/wiki/references/utilities/statuses.md +740 -0
- package/wiki/get-started/best-practices/api-usage-examples.md +0 -266
- package/wiki/get-started/best-practices/architectural-patterns.md +0 -170
- package/wiki/get-started/best-practices/data-modeling.md +0 -177
- package/wiki/get-started/best-practices/deployment-strategies.md +0 -121
- package/wiki/get-started/best-practices/performance-optimization.md +0 -97
- package/wiki/get-started/best-practices/security-guidelines.md +0 -99
- package/wiki/get-started/core-concepts/persistent.md +0 -539
- package/wiki/get-started/index.md +0 -65
- package/wiki/get-started/philosophy.md +0 -296
- package/wiki/get-started/prerequisites.md +0 -113
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: JSX/HTML Utilities Reference
|
|
3
|
+
description: Utilities for HTML and JSX responses in OpenAPI routes
|
|
4
|
+
difficulty: beginner
|
|
5
|
+
lastUpdated: 2026-01-03
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# JSX/HTML Utility
|
|
9
|
+
|
|
10
|
+
The JSX utility provides helper functions for defining HTML/JSX response schemas in OpenAPI routes. These utilities are companions to `jsonContent` and `jsonResponse` but for HTML content type.
|
|
11
|
+
|
|
12
|
+
**File:** `packages/core/src/utilities/jsx.utility.ts`
|
|
13
|
+
|
|
14
|
+
## Quick Reference
|
|
15
|
+
|
|
16
|
+
| Function | Purpose | Returns |
|
|
17
|
+
|----------|---------|---------|
|
|
18
|
+
| `htmlContent()` | Create HTML content configuration | OpenAPI content object |
|
|
19
|
+
| `htmlResponse()` | Create HTML response with error handling | OpenAPI response object |
|
|
20
|
+
|
|
21
|
+
## When to Use
|
|
22
|
+
|
|
23
|
+
Use these utilities when creating routes that:
|
|
24
|
+
- Render HTML pages using Hono JSX
|
|
25
|
+
- Return server-side rendered content
|
|
26
|
+
- Serve HTML documentation or views
|
|
27
|
+
- Generate HTML emails or reports
|
|
28
|
+
|
|
29
|
+
## htmlContent()
|
|
30
|
+
|
|
31
|
+
Creates a standard OpenAPI content object for `text/html` responses.
|
|
32
|
+
|
|
33
|
+
### Signature
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
function htmlContent(opts: {
|
|
37
|
+
description: string;
|
|
38
|
+
required?: boolean;
|
|
39
|
+
}): {
|
|
40
|
+
description: string;
|
|
41
|
+
content: {
|
|
42
|
+
'text/html': {
|
|
43
|
+
schema: ZodString;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
required: boolean;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Parameters
|
|
51
|
+
|
|
52
|
+
| Parameter | Type | Required | Default | Description |
|
|
53
|
+
|-----------|------|----------|---------|-------------|
|
|
54
|
+
| `description` | `string` | Yes | - | Description of the HTML content |
|
|
55
|
+
| `required` | `boolean` | No | `false` | Whether the content is required |
|
|
56
|
+
|
|
57
|
+
### Returns
|
|
58
|
+
|
|
59
|
+
Returns an OpenAPI content configuration object with:
|
|
60
|
+
- `description`: The provided description
|
|
61
|
+
- `content`: Content type configuration for `text/html`
|
|
62
|
+
- `required`: Whether the content is required
|
|
63
|
+
|
|
64
|
+
### Example
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { htmlContent } from '@venizia/ignis';
|
|
68
|
+
|
|
69
|
+
const pageContent = htmlContent({
|
|
70
|
+
description: 'HTML page content',
|
|
71
|
+
required: true,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Result:
|
|
75
|
+
// {
|
|
76
|
+
// description: 'HTML page content',
|
|
77
|
+
// content: {
|
|
78
|
+
// 'text/html': {
|
|
79
|
+
// schema: z.string().openapi({
|
|
80
|
+
// description: 'HTML content',
|
|
81
|
+
// example: '<!DOCTYPE html><html>...</html>',
|
|
82
|
+
// }),
|
|
83
|
+
// },
|
|
84
|
+
// },
|
|
85
|
+
// required: true,
|
|
86
|
+
// }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## htmlResponse()
|
|
92
|
+
|
|
93
|
+
Creates a standard OpenAPI response object for HTML endpoints, including success (200 OK) HTML response and JSON error responses for 4xx/5xx status codes.
|
|
94
|
+
|
|
95
|
+
### Signature
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
function htmlResponse(opts: {
|
|
99
|
+
description: string;
|
|
100
|
+
required?: boolean;
|
|
101
|
+
}): {
|
|
102
|
+
200: typeof htmlContent;
|
|
103
|
+
'4xx | 5xx': {
|
|
104
|
+
description: 'Error Response';
|
|
105
|
+
content: {
|
|
106
|
+
'application/json': {
|
|
107
|
+
schema: ErrorSchema;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Parameters
|
|
115
|
+
|
|
116
|
+
| Parameter | Type | Required | Default | Description |
|
|
117
|
+
|-----------|------|----------|---------|-------------|
|
|
118
|
+
| `description` | `string` | Yes | - | Description of the successful HTML response |
|
|
119
|
+
| `required` | `boolean` | No | `false` | Whether the content is required |
|
|
120
|
+
|
|
121
|
+
### Returns
|
|
122
|
+
|
|
123
|
+
Returns an OpenAPI responses object with:
|
|
124
|
+
- `200`: Success response with HTML content
|
|
125
|
+
- `4xx | 5xx`: Error responses with JSON error schema
|
|
126
|
+
|
|
127
|
+
### Example
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import { htmlResponse } from '@venizia/ignis';
|
|
131
|
+
|
|
132
|
+
this.defineRoute({
|
|
133
|
+
configs: {
|
|
134
|
+
path: '/dashboard',
|
|
135
|
+
method: 'get',
|
|
136
|
+
responses: htmlResponse({
|
|
137
|
+
description: 'Dashboard HTML page',
|
|
138
|
+
}),
|
|
139
|
+
},
|
|
140
|
+
handler: async (context) => {
|
|
141
|
+
return context.html(
|
|
142
|
+
<html>
|
|
143
|
+
<head>
|
|
144
|
+
<title>Dashboard</title>
|
|
145
|
+
</head>
|
|
146
|
+
<body>
|
|
147
|
+
<h1>Welcome to Dashboard</h1>
|
|
148
|
+
</body>
|
|
149
|
+
</html>
|
|
150
|
+
);
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Usage Examples
|
|
158
|
+
|
|
159
|
+
### Basic HTML Route
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { BaseController, get, htmlResponse } from '@venizia/ignis';
|
|
163
|
+
|
|
164
|
+
export class PageController extends BaseController {
|
|
165
|
+
@get({
|
|
166
|
+
path: '/home',
|
|
167
|
+
responses: htmlResponse({
|
|
168
|
+
description: 'Home page HTML',
|
|
169
|
+
}),
|
|
170
|
+
})
|
|
171
|
+
async getHomePage() {
|
|
172
|
+
return this.context.html(
|
|
173
|
+
<html>
|
|
174
|
+
<head>
|
|
175
|
+
<title>Home</title>
|
|
176
|
+
</head>
|
|
177
|
+
<body>
|
|
178
|
+
<h1>Welcome Home</h1>
|
|
179
|
+
</body>
|
|
180
|
+
</html>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### HTML Email Preview
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { BaseController, get, htmlResponse, TRouteContext, HTTP, z } from '@venizia/ignis';
|
|
190
|
+
|
|
191
|
+
const EmailRoutes = {
|
|
192
|
+
PREVIEW: {
|
|
193
|
+
method: HTTP.Methods.GET,
|
|
194
|
+
path: '/preview/:templateId',
|
|
195
|
+
request: {
|
|
196
|
+
params: z.object({ templateId: z.string() }),
|
|
197
|
+
},
|
|
198
|
+
responses: htmlResponse({
|
|
199
|
+
description: 'Email template preview',
|
|
200
|
+
}),
|
|
201
|
+
},
|
|
202
|
+
} as const;
|
|
203
|
+
|
|
204
|
+
export class EmailController extends BaseController {
|
|
205
|
+
@get({ configs: EmailRoutes.PREVIEW })
|
|
206
|
+
async previewTemplate(c: TRouteContext<typeof EmailRoutes.PREVIEW>) {
|
|
207
|
+
const { templateId } = c.req.valid('param');
|
|
208
|
+
const template = await this.emailService.getTemplate(templateId);
|
|
209
|
+
|
|
210
|
+
return c.html(
|
|
211
|
+
<html>
|
|
212
|
+
<head>
|
|
213
|
+
<title>Email Preview: {template.subject}</title>
|
|
214
|
+
</head>
|
|
215
|
+
<body>
|
|
216
|
+
<div dangerouslySetInnerHTML={{ __html: template.html }} />
|
|
217
|
+
</body>
|
|
218
|
+
</html>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Documentation Page
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { BaseController, get, htmlResponse, TRouteContext, HTTP, z } from '@venizia/ignis';
|
|
228
|
+
|
|
229
|
+
const DocsRoutes = {
|
|
230
|
+
GET_SECTION: {
|
|
231
|
+
method: HTTP.Methods.GET,
|
|
232
|
+
path: '/docs/:section',
|
|
233
|
+
request: {
|
|
234
|
+
params: z.object({ section: z.string() }),
|
|
235
|
+
},
|
|
236
|
+
responses: htmlResponse({
|
|
237
|
+
description: 'API documentation page',
|
|
238
|
+
}),
|
|
239
|
+
},
|
|
240
|
+
} as const;
|
|
241
|
+
|
|
242
|
+
export class DocsController extends BaseController {
|
|
243
|
+
@get({ configs: DocsRoutes.GET_SECTION })
|
|
244
|
+
async getDocumentation(c: TRouteContext<typeof DocsRoutes.GET_SECTION>) {
|
|
245
|
+
const { section } = c.req.valid('param');
|
|
246
|
+
const content = await this.docsService.getSection(section);
|
|
247
|
+
|
|
248
|
+
return c.html(
|
|
249
|
+
<html>
|
|
250
|
+
<head>
|
|
251
|
+
<title>Docs - {content.title}</title>
|
|
252
|
+
<link rel="stylesheet" href="/styles/docs.css" />
|
|
253
|
+
</head>
|
|
254
|
+
<body>
|
|
255
|
+
<nav>
|
|
256
|
+
<a href="/docs/getting-started">Getting Started</a>
|
|
257
|
+
<a href="/docs/api">API Reference</a>
|
|
258
|
+
</nav>
|
|
259
|
+
<main>
|
|
260
|
+
<h1>{content.title}</h1>
|
|
261
|
+
<div dangerouslySetInnerHTML={{ __html: content.html }} />
|
|
262
|
+
</main>
|
|
263
|
+
</body>
|
|
264
|
+
</html>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Admin Dashboard
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
import { BaseController, get, htmlResponse } from '@venizia/ignis';
|
|
274
|
+
import { authenticate } from '../middleware/auth';
|
|
275
|
+
|
|
276
|
+
export class AdminController extends BaseController {
|
|
277
|
+
@get({
|
|
278
|
+
path: '/admin',
|
|
279
|
+
middleware: [authenticate({ role: 'admin' })],
|
|
280
|
+
responses: htmlResponse({
|
|
281
|
+
description: 'Admin dashboard',
|
|
282
|
+
}),
|
|
283
|
+
})
|
|
284
|
+
async getDashboard() {
|
|
285
|
+
const stats = await this.statsService.getAdminStats();
|
|
286
|
+
|
|
287
|
+
return this.context.html(
|
|
288
|
+
<html>
|
|
289
|
+
<head>
|
|
290
|
+
<title>Admin Dashboard</title>
|
|
291
|
+
<script src="/js/dashboard.js" defer />
|
|
292
|
+
</head>
|
|
293
|
+
<body>
|
|
294
|
+
<div class="dashboard">
|
|
295
|
+
<h1>Admin Dashboard</h1>
|
|
296
|
+
<div class="stats">
|
|
297
|
+
<div class="stat-card">
|
|
298
|
+
<h3>Total Users</h3>
|
|
299
|
+
<p>{stats.totalUsers}</p>
|
|
300
|
+
</div>
|
|
301
|
+
<div class="stat-card">
|
|
302
|
+
<h3>Active Sessions</h3>
|
|
303
|
+
<p>{stats.activeSessions}</p>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
</body>
|
|
308
|
+
</html>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Comparison with JSON Utilities
|
|
317
|
+
|
|
318
|
+
### htmlContent vs jsonContent
|
|
319
|
+
|
|
320
|
+
| Aspect | `htmlContent()` | `jsonContent()` |
|
|
321
|
+
|--------|----------------|-----------------|
|
|
322
|
+
| **Content-Type** | `text/html` | `application/json` |
|
|
323
|
+
| **Schema** | `z.string()` | Custom Zod schema |
|
|
324
|
+
| **Use Case** | HTML pages, JSX rendering | API responses, data |
|
|
325
|
+
| **Example** | HTML document string | JSON object |
|
|
326
|
+
|
|
327
|
+
### htmlResponse vs jsonResponse
|
|
328
|
+
|
|
329
|
+
| Aspect | `htmlResponse()` | `jsonResponse()` |
|
|
330
|
+
|--------|------------------|------------------|
|
|
331
|
+
| **Success Type** | `text/html` (200) | `application/json` (200) |
|
|
332
|
+
| **Error Type** | `application/json` (4xx/5xx) | `application/json` (4xx/5xx) |
|
|
333
|
+
| **Use Case** | Web pages | REST APIs |
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Best Practices
|
|
338
|
+
|
|
339
|
+
### 1. Use for Server-Side Rendering
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// ✅ Good: Use htmlResponse for SSR routes
|
|
343
|
+
const ProfileConfig = {
|
|
344
|
+
method: HTTP.Methods.GET,
|
|
345
|
+
path: '/profile/:userId',
|
|
346
|
+
request: { params: z.object({ userId: z.string() }) },
|
|
347
|
+
responses: htmlResponse({ description: 'User profile page' }),
|
|
348
|
+
} as const;
|
|
349
|
+
|
|
350
|
+
@get({ configs: ProfileConfig })
|
|
351
|
+
async getUserProfile(c: TRouteContext<typeof ProfileConfig>) {
|
|
352
|
+
const { userId } = c.req.valid('param');
|
|
353
|
+
const user = await this.userService.getUser(userId);
|
|
354
|
+
return c.html(<UserProfile user={user} />);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ❌ Bad: Don't use htmlResponse for API endpoints
|
|
358
|
+
const BadConfig = {
|
|
359
|
+
method: HTTP.Methods.GET,
|
|
360
|
+
path: '/api/users/:userId',
|
|
361
|
+
request: { params: z.object({ userId: z.string() }) },
|
|
362
|
+
responses: htmlResponse({ description: 'User data' }), // Wrong!
|
|
363
|
+
} as const;
|
|
364
|
+
|
|
365
|
+
@get({ configs: BadConfig })
|
|
366
|
+
async getUser(c: TRouteContext<typeof BadConfig>) {
|
|
367
|
+
const { userId } = c.req.valid('param');
|
|
368
|
+
return { id: userId, name: 'John' }; // Should use jsonResponse
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### 2. Combine with Authentication
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
// ✅ Good: Protect HTML routes with auth
|
|
376
|
+
const SettingsConfig = {
|
|
377
|
+
method: HTTP.Methods.GET,
|
|
378
|
+
path: '/admin/settings',
|
|
379
|
+
authStrategies: [Authentication.STRATEGY_JWT],
|
|
380
|
+
responses: htmlResponse({ description: 'Settings page' }),
|
|
381
|
+
} as const;
|
|
382
|
+
|
|
383
|
+
@get({ configs: SettingsConfig })
|
|
384
|
+
async getSettings(c: TRouteContext<typeof SettingsConfig>) {
|
|
385
|
+
return c.html(<SettingsPage />);
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### 3. Error Handling
|
|
390
|
+
|
|
391
|
+
HTML routes automatically return JSON errors for 4xx/5xx:
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
const ArticleConfig = {
|
|
395
|
+
method: HTTP.Methods.GET,
|
|
396
|
+
path: '/article/:id',
|
|
397
|
+
request: { params: z.object({ id: z.string() }) },
|
|
398
|
+
responses: htmlResponse({ description: 'Article page' }),
|
|
399
|
+
} as const;
|
|
400
|
+
|
|
401
|
+
@get({ configs: ArticleConfig })
|
|
402
|
+
async getArticle(c: TRouteContext<typeof ArticleConfig>) {
|
|
403
|
+
const { id } = c.req.valid('param');
|
|
404
|
+
const article = await this.articleService.findById(id);
|
|
405
|
+
|
|
406
|
+
if (!article) {
|
|
407
|
+
// Returns JSON error: { message: 'Not found', statusCode: 404 }
|
|
408
|
+
throw new NotFoundError('Article not found');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return c.html(<ArticlePage article={article} />);
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### 4. SEO-Friendly Metadata
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
const BlogConfig = {
|
|
419
|
+
method: HTTP.Methods.GET,
|
|
420
|
+
path: '/blog/:slug',
|
|
421
|
+
request: { params: z.object({ slug: z.string() }) },
|
|
422
|
+
responses: htmlResponse({ description: 'Blog post page' }),
|
|
423
|
+
} as const;
|
|
424
|
+
|
|
425
|
+
@get({ configs: BlogConfig })
|
|
426
|
+
async getBlogPost(c: TRouteContext<typeof BlogConfig>) {
|
|
427
|
+
const { slug } = c.req.valid('param');
|
|
428
|
+
const post = await this.blogService.getBySlug(slug);
|
|
429
|
+
|
|
430
|
+
return c.html(
|
|
431
|
+
<html>
|
|
432
|
+
<head>
|
|
433
|
+
<title>{post.title} | My Blog</title>
|
|
434
|
+
<meta name="description" content={post.excerpt} />
|
|
435
|
+
<meta property="og:title" content={post.title} />
|
|
436
|
+
<meta property="og:description" content={post.excerpt} />
|
|
437
|
+
<meta property="og:image" content={post.coverImage} />
|
|
438
|
+
</head>
|
|
439
|
+
<body>
|
|
440
|
+
<article>
|
|
441
|
+
<h1>{post.title}</h1>
|
|
442
|
+
<div dangerouslySetInnerHTML={{ __html: post.content }} />
|
|
443
|
+
</article>
|
|
444
|
+
</body>
|
|
445
|
+
</html>
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Integration with Hono JSX
|
|
453
|
+
|
|
454
|
+
IGNIS uses Hono's built-in JSX support. Make sure to configure your `tsconfig.json`:
|
|
455
|
+
|
|
456
|
+
```json
|
|
457
|
+
{
|
|
458
|
+
"compilerOptions": {
|
|
459
|
+
"jsx": "react-jsx",
|
|
460
|
+
"jsxImportSource": "hono/jsx"
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### JSX Components
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
// components/Layout.tsx
|
|
469
|
+
export const Layout = (props: { title: string; children: any }) => {
|
|
470
|
+
return (
|
|
471
|
+
<html>
|
|
472
|
+
<head>
|
|
473
|
+
<title>{props.title}</title>
|
|
474
|
+
<link rel="stylesheet" href="/styles/main.css" />
|
|
475
|
+
</head>
|
|
476
|
+
<body>
|
|
477
|
+
<header>
|
|
478
|
+
<nav>
|
|
479
|
+
<a href="/">Home</a>
|
|
480
|
+
<a href="/about">About</a>
|
|
481
|
+
</nav>
|
|
482
|
+
</header>
|
|
483
|
+
<main>{props.children}</main>
|
|
484
|
+
<footer>
|
|
485
|
+
<p>© 2026 My App</p>
|
|
486
|
+
</footer>
|
|
487
|
+
</body>
|
|
488
|
+
</html>
|
|
489
|
+
);
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
// controller.ts
|
|
493
|
+
import { Layout } from './components/Layout';
|
|
494
|
+
|
|
495
|
+
@get({
|
|
496
|
+
path: '/',
|
|
497
|
+
responses: htmlResponse({ description: 'Home page' }),
|
|
498
|
+
})
|
|
499
|
+
async getHome() {
|
|
500
|
+
return this.context.html(
|
|
501
|
+
<Layout title="Home">
|
|
502
|
+
<h1>Welcome to My App</h1>
|
|
503
|
+
<p>This is the home page.</p>
|
|
504
|
+
</Layout>
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
## Common Pitfalls
|
|
512
|
+
|
|
513
|
+
### Pitfall 1: Missing HTML Wrapper
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
// ❌ Bad: Incomplete HTML
|
|
517
|
+
@get({
|
|
518
|
+
path: '/page',
|
|
519
|
+
responses: htmlResponse({ description: 'Page' }),
|
|
520
|
+
})
|
|
521
|
+
async getPage() {
|
|
522
|
+
return this.context.html(<div>Hello</div>); // Missing <html>, <head>, <body>
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ✅ Good: Complete HTML document
|
|
526
|
+
@get({
|
|
527
|
+
path: '/page',
|
|
528
|
+
responses: htmlResponse({ description: 'Page' }),
|
|
529
|
+
})
|
|
530
|
+
async getPage() {
|
|
531
|
+
return this.context.html(
|
|
532
|
+
<html>
|
|
533
|
+
<head><title>Page</title></head>
|
|
534
|
+
<body><div>Hello</div></body>
|
|
535
|
+
</html>
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
### Pitfall 2: Using htmlResponse for APIs
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
// ❌ Bad: HTML response for API
|
|
544
|
+
@get({
|
|
545
|
+
path: '/api/users',
|
|
546
|
+
responses: htmlResponse({ description: 'Users' }),
|
|
547
|
+
})
|
|
548
|
+
async getUsers() {
|
|
549
|
+
return { users: [...] }; // Should return HTML or use jsonResponse
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ✅ Good: Use jsonResponse for APIs
|
|
553
|
+
@get({
|
|
554
|
+
path: '/api/users',
|
|
555
|
+
responses: jsonResponse({
|
|
556
|
+
description: 'Users list',
|
|
557
|
+
schema: z.object({ users: z.array(UserSchema) }),
|
|
558
|
+
}),
|
|
559
|
+
})
|
|
560
|
+
async getUsers() {
|
|
561
|
+
return { users: await this.userService.findAll() };
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
## See Also
|
|
568
|
+
|
|
569
|
+
- **Related References:**
|
|
570
|
+
- [Schema Utility](./schema.md) - JSON content and response helpers
|
|
571
|
+
- [Controllers](../base/controllers.md) - Defining routes and handlers
|
|
572
|
+
- [OpenAPI Component](../components/swagger.md) - API documentation
|
|
573
|
+
|
|
574
|
+
- **External Resources:**
|
|
575
|
+
- [Hono JSX Documentation](https://hono.dev/guides/jsx)
|
|
576
|
+
- [OpenAPI Specification](https://swagger.io/specification/)
|
|
577
|
+
- [React JSX (for reference)](https://react.dev/learn/writing-markup-with-jsx)
|
|
@@ -139,7 +139,6 @@ sanitizeFilename(''); // Returns: 'download'
|
|
|
139
139
|
sanitizeFilename('..'); // Returns: 'download'
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
---
|
|
143
142
|
|
|
144
143
|
### `encodeRFC5987`
|
|
145
144
|
|
|
@@ -160,7 +159,6 @@ encodeRFC5987('my document.pdf'); // Returns: 'my%20document.pdf'
|
|
|
160
159
|
encodeRFC5987('файл.txt'); // Returns: '%D1%84%D0%B0%D0%B9%D0%BB.txt'
|
|
161
160
|
```
|
|
162
161
|
|
|
163
|
-
---
|
|
164
162
|
|
|
165
163
|
## Complete File Download Example
|
|
166
164
|
|