@engjts/nexus 0.1.7 → 0.1.9
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/advanced/playground/generatePlaygroundHTML.d.ts.map +1 -1
- package/dist/advanced/playground/generatePlaygroundHTML.js +107 -0
- package/dist/advanced/playground/generatePlaygroundHTML.js.map +1 -1
- package/dist/advanced/playground/playground.d.ts +19 -0
- package/dist/advanced/playground/playground.d.ts.map +1 -1
- package/dist/advanced/playground/playground.js +70 -0
- package/dist/advanced/playground/playground.js.map +1 -1
- package/dist/advanced/playground/types.d.ts +20 -0
- package/dist/advanced/playground/types.d.ts.map +1 -1
- package/dist/core/application.d.ts +14 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/application.js +173 -71
- package/dist/core/application.js.map +1 -1
- package/dist/core/context-pool.d.ts +2 -13
- package/dist/core/context-pool.d.ts.map +1 -1
- package/dist/core/context-pool.js +7 -45
- package/dist/core/context-pool.js.map +1 -1
- package/dist/core/context.d.ts +108 -5
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +449 -53
- package/dist/core/context.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +9 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/middleware.d.ts +6 -0
- package/dist/core/middleware.d.ts.map +1 -1
- package/dist/core/middleware.js +83 -84
- package/dist/core/middleware.js.map +1 -1
- package/dist/core/performance/fast-json.d.ts +149 -0
- package/dist/core/performance/fast-json.d.ts.map +1 -0
- package/dist/core/performance/fast-json.js +473 -0
- package/dist/core/performance/fast-json.js.map +1 -0
- package/dist/core/router/file-router.d.ts +20 -7
- package/dist/core/router/file-router.d.ts.map +1 -1
- package/dist/core/router/file-router.js +41 -13
- package/dist/core/router/file-router.js.map +1 -1
- package/dist/core/router/index.d.ts +6 -0
- package/dist/core/router/index.d.ts.map +1 -1
- package/dist/core/router/index.js +33 -6
- package/dist/core/router/index.js.map +1 -1
- package/dist/core/router/radix-tree.d.ts +4 -1
- package/dist/core/router/radix-tree.d.ts.map +1 -1
- package/dist/core/router/radix-tree.js +7 -3
- package/dist/core/router/radix-tree.js.map +1 -1
- package/dist/core/serializer.d.ts +251 -0
- package/dist/core/serializer.d.ts.map +1 -0
- package/dist/core/serializer.js +290 -0
- package/dist/core/serializer.js.map +1 -0
- package/dist/core/types.d.ts +39 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/documentation/01-getting-started.md +0 -240
- package/documentation/02-context.md +0 -335
- package/documentation/03-routing.md +0 -397
- package/documentation/04-middleware.md +0 -483
- package/documentation/05-validation.md +0 -514
- package/documentation/06-error-handling.md +0 -465
- package/documentation/07-performance.md +0 -364
- package/documentation/08-adapters.md +0 -470
- package/documentation/09-api-reference.md +0 -548
- package/documentation/10-examples.md +0 -582
- package/documentation/11-deployment.md +0 -477
- package/documentation/12-sentry.md +0 -620
- package/documentation/13-sentry-data-storage.md +0 -996
- package/documentation/14-sentry-data-reference.md +0 -457
- package/documentation/15-sentry-summary.md +0 -409
- package/documentation/16-alerts-system.md +0 -745
- package/documentation/17-alert-adapters.md +0 -696
- package/documentation/18-alerts-implementation-summary.md +0 -385
- package/documentation/19-class-based-routing.md +0 -840
- package/documentation/20-websocket-realtime.md +0 -813
- package/documentation/21-cache-system.md +0 -510
- package/documentation/22-job-queue.md +0 -772
- package/documentation/23-sentry-plugin.md +0 -551
- package/documentation/24-testing-utilities.md +0 -1287
- package/documentation/25-api-versioning.md +0 -533
- package/documentation/26-context-store.md +0 -607
- package/documentation/27-dependency-injection.md +0 -329
- package/documentation/28-lifecycle-hooks.md +0 -521
- package/documentation/29-package-structure.md +0 -196
- package/documentation/30-plugin-system.md +0 -414
- package/documentation/31-jwt-authentication.md +0 -597
- package/documentation/32-cli.md +0 -268
- package/documentation/ALERTS-COMPLETE-SUMMARY.md +0 -429
- package/documentation/ALERTS-INDEX.md +0 -330
- package/documentation/ALERTS-QUICK-REFERENCE.md +0 -286
- package/documentation/README.md +0 -178
- package/documentation/index.html +0 -34
- package/modern_framework_paper.md +0 -1870
- package/public/css/style.css +0 -87
- package/public/index.html +0 -34
- package/public/js/app.js +0 -27
- package/src/advanced/cache/InMemoryCacheStore.ts +0 -68
- package/src/advanced/cache/MultiTierCache.ts +0 -194
- package/src/advanced/cache/RedisCacheStore.ts +0 -341
- package/src/advanced/cache/index.ts +0 -5
- package/src/advanced/cache/types.ts +0 -40
- package/src/advanced/graphql/SimpleDataLoader.ts +0 -42
- package/src/advanced/graphql/index.ts +0 -22
- package/src/advanced/graphql/server.ts +0 -252
- package/src/advanced/graphql/types.ts +0 -42
- package/src/advanced/jobs/InMemoryQueueStore.ts +0 -68
- package/src/advanced/jobs/JobQueue.ts +0 -556
- package/src/advanced/jobs/RedisQueueStore.ts +0 -367
- package/src/advanced/jobs/index.ts +0 -5
- package/src/advanced/jobs/types.ts +0 -70
- package/src/advanced/observability/APMManager.ts +0 -163
- package/src/advanced/observability/AlertManager.ts +0 -109
- package/src/advanced/observability/MetricRegistry.ts +0 -151
- package/src/advanced/observability/ObservabilityCenter.ts +0 -304
- package/src/advanced/observability/StructuredLogger.ts +0 -154
- package/src/advanced/observability/TracingManager.ts +0 -117
- package/src/advanced/observability/adapters.ts +0 -304
- package/src/advanced/observability/createObservabilityMiddleware.ts +0 -63
- package/src/advanced/observability/index.ts +0 -11
- package/src/advanced/observability/types.ts +0 -174
- package/src/advanced/playground/extractPathParams.ts +0 -6
- package/src/advanced/playground/generateFieldExample.ts +0 -31
- package/src/advanced/playground/generatePlaygroundHTML.ts +0 -1849
- package/src/advanced/playground/generateSummary.ts +0 -19
- package/src/advanced/playground/getTagFromPath.ts +0 -9
- package/src/advanced/playground/index.ts +0 -8
- package/src/advanced/playground/playground.ts +0 -170
- package/src/advanced/playground/types.ts +0 -20
- package/src/advanced/playground/zodToExample.ts +0 -16
- package/src/advanced/playground/zodToParams.ts +0 -15
- package/src/advanced/postman/buildAuth.ts +0 -31
- package/src/advanced/postman/buildBody.ts +0 -15
- package/src/advanced/postman/buildQueryParams.ts +0 -27
- package/src/advanced/postman/buildRequestItem.ts +0 -36
- package/src/advanced/postman/buildResponses.ts +0 -11
- package/src/advanced/postman/buildUrl.ts +0 -33
- package/src/advanced/postman/capitalize.ts +0 -4
- package/src/advanced/postman/generateCollection.ts +0 -59
- package/src/advanced/postman/generateEnvironment.ts +0 -34
- package/src/advanced/postman/generateExampleFromZod.ts +0 -21
- package/src/advanced/postman/generateFieldExample.ts +0 -45
- package/src/advanced/postman/generateName.ts +0 -20
- package/src/advanced/postman/generateUUID.ts +0 -11
- package/src/advanced/postman/getTagFromPath.ts +0 -10
- package/src/advanced/postman/index.ts +0 -28
- package/src/advanced/postman/postman.ts +0 -156
- package/src/advanced/postman/slugify.ts +0 -7
- package/src/advanced/postman/types.ts +0 -140
- package/src/advanced/realtime/index.ts +0 -18
- package/src/advanced/realtime/websocket.ts +0 -231
- package/src/advanced/sentry/index.ts +0 -1236
- package/src/advanced/sentry/types.ts +0 -355
- package/src/advanced/static/generateDirectoryListing.ts +0 -47
- package/src/advanced/static/generateETag.ts +0 -7
- package/src/advanced/static/getMimeType.ts +0 -9
- package/src/advanced/static/index.ts +0 -32
- package/src/advanced/static/isSafePath.ts +0 -13
- package/src/advanced/static/publicDir.ts +0 -21
- package/src/advanced/static/serveStatic.ts +0 -225
- package/src/advanced/static/spa.ts +0 -24
- package/src/advanced/static/types.ts +0 -159
- package/src/advanced/swagger/SwaggerGenerator.ts +0 -66
- package/src/advanced/swagger/buildOperation.ts +0 -61
- package/src/advanced/swagger/buildParameters.ts +0 -61
- package/src/advanced/swagger/buildRequestBody.ts +0 -21
- package/src/advanced/swagger/buildResponses.ts +0 -54
- package/src/advanced/swagger/capitalize.ts +0 -5
- package/src/advanced/swagger/convertPath.ts +0 -9
- package/src/advanced/swagger/createSwagger.ts +0 -12
- package/src/advanced/swagger/generateOperationId.ts +0 -21
- package/src/advanced/swagger/generateSpec.ts +0 -105
- package/src/advanced/swagger/generateSummary.ts +0 -24
- package/src/advanced/swagger/generateSwaggerUI.ts +0 -70
- package/src/advanced/swagger/generateThemeCss.ts +0 -53
- package/src/advanced/swagger/index.ts +0 -25
- package/src/advanced/swagger/swagger.ts +0 -237
- package/src/advanced/swagger/types.ts +0 -206
- package/src/advanced/swagger/zodFieldToOpenAPI.ts +0 -94
- package/src/advanced/swagger/zodSchemaToOpenAPI.ts +0 -50
- package/src/advanced/swagger/zodToOpenAPI.ts +0 -22
- package/src/advanced/testing/factory.ts +0 -509
- package/src/advanced/testing/harness.ts +0 -612
- package/src/advanced/testing/index.ts +0 -430
- package/src/advanced/testing/load-test.ts +0 -618
- package/src/advanced/testing/mock-server.ts +0 -498
- package/src/advanced/testing/mock.ts +0 -670
- package/src/cli/bin.ts +0 -9
- package/src/cli/cli.ts +0 -158
- package/src/cli/commands/add.ts +0 -178
- package/src/cli/commands/build.ts +0 -73
- package/src/cli/commands/create.ts +0 -166
- package/src/cli/commands/dev.ts +0 -85
- package/src/cli/commands/generate.ts +0 -99
- package/src/cli/commands/help.ts +0 -95
- package/src/cli/commands/init.ts +0 -91
- package/src/cli/commands/version.ts +0 -38
- package/src/cli/index.ts +0 -6
- package/src/cli/templates/generators.ts +0 -359
- package/src/cli/templates/index.ts +0 -680
- package/src/cli/utils/exec.ts +0 -52
- package/src/cli/utils/file-system.ts +0 -78
- package/src/cli/utils/logger.ts +0 -111
- package/src/core/adapter.ts +0 -88
- package/src/core/application.ts +0 -1335
- package/src/core/context-pool.ts +0 -127
- package/src/core/context.ts +0 -412
- package/src/core/index.ts +0 -80
- package/src/core/middleware.ts +0 -262
- package/src/core/performance/buffer-pool.ts +0 -108
- package/src/core/performance/middleware-optimizer.ts +0 -162
- package/src/core/plugin/PluginManager.ts +0 -435
- package/src/core/plugin/builder.ts +0 -358
- package/src/core/plugin/index.ts +0 -50
- package/src/core/plugin/types.ts +0 -214
- package/src/core/router/file-router.ts +0 -594
- package/src/core/router/index.ts +0 -227
- package/src/core/router/radix-tree.ts +0 -226
- package/src/core/store/index.ts +0 -30
- package/src/core/store/registry.ts +0 -178
- package/src/core/store/request-store.ts +0 -240
- package/src/core/store/types.ts +0 -233
- package/src/core/types.ts +0 -574
- package/src/database/adapter.ts +0 -35
- package/src/database/adapters/index.ts +0 -1
- package/src/database/adapters/mysql.ts +0 -669
- package/src/database/database.ts +0 -70
- package/src/database/dialect.ts +0 -388
- package/src/database/index.ts +0 -12
- package/src/database/migrations.ts +0 -86
- package/src/database/optimizer.ts +0 -125
- package/src/database/query-builder.ts +0 -404
- package/src/database/realtime.ts +0 -53
- package/src/database/schema.ts +0 -71
- package/src/database/transactions.ts +0 -56
- package/src/database/types.ts +0 -87
- package/src/deployment/cluster.ts +0 -471
- package/src/deployment/config.ts +0 -454
- package/src/deployment/docker.ts +0 -599
- package/src/deployment/graceful-shutdown.ts +0 -373
- package/src/deployment/index.ts +0 -56
- package/src/index.ts +0 -264
- package/src/security/adapter.ts +0 -318
- package/src/security/auth/JWTPlugin.ts +0 -234
- package/src/security/auth/JWTProvider.ts +0 -316
- package/src/security/auth/adapter.ts +0 -12
- package/src/security/auth/jwt.ts +0 -234
- package/src/security/auth/middleware.ts +0 -188
- package/src/security/csrf.ts +0 -220
- package/src/security/headers.ts +0 -108
- package/src/security/index.ts +0 -60
- package/src/security/rate-limit/adapter.ts +0 -7
- package/src/security/rate-limit/memory.ts +0 -108
- package/src/security/rate-limit/middleware.ts +0 -181
- package/src/security/sanitization.ts +0 -75
- package/src/security/types.ts +0 -240
- package/src/security/utils.ts +0 -52
- package/tsconfig.json +0 -39
|
@@ -1,840 +0,0 @@
|
|
|
1
|
-
# Class-Based Routing
|
|
2
|
-
|
|
3
|
-
Nexus Framework mendukung **class-based routing** yang memungkinkan kamu mengorganisir route dalam bentuk class. Pendekatan ini sangat berguna untuk:
|
|
4
|
-
|
|
5
|
-
- ✅ Memisahkan logic route ke file terpisah
|
|
6
|
-
- ✅ Reusability dan testability yang lebih baik
|
|
7
|
-
- ✅ Clean code architecture
|
|
8
|
-
- ✅ Dependency injection friendly
|
|
9
|
-
- ✅ **Lifecycle hooks** untuk kontrol penuh atas request/response
|
|
10
|
-
|
|
11
|
-
## Quick Start
|
|
12
|
-
|
|
13
|
-
### Menggunakan `Route` Abstract Class (Recommended)
|
|
14
|
-
|
|
15
|
-
Gunakan abstract class `Route` jika kamu ingin **TypeScript memaksa implementasi handler**:
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
import { createApp, Route, Context } from 'nexus';
|
|
19
|
-
import { z } from 'zod';
|
|
20
|
-
|
|
21
|
-
// TypeScript akan ERROR kalau handler atau pathName tidak di-implement!
|
|
22
|
-
class UserRegister extends Route {
|
|
23
|
-
pathName = '/api/users/register';
|
|
24
|
-
|
|
25
|
-
// ✅ Optional: Hook sebelum handler
|
|
26
|
-
async onBefore(ctx: Context) {
|
|
27
|
-
console.log('Before handler');
|
|
28
|
-
// Return value untuk skip handler
|
|
29
|
-
// return { redirect: '/login' };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// ✅ Optional: Hook setelah handler sukses
|
|
33
|
-
async onAfter(ctx: Context, result: any) {
|
|
34
|
-
return { ...result, timestamp: Date.now() };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ✅ Optional: Custom error handling
|
|
38
|
-
async onError(ctx: Context, error: Error) {
|
|
39
|
-
return { error: error.message, code: 'CUSTOM_ERROR' };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
schema() {
|
|
43
|
-
return {
|
|
44
|
-
body: z.object({
|
|
45
|
-
email: z.string().email(),
|
|
46
|
-
password: z.string().min(8)
|
|
47
|
-
})
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
meta() {
|
|
52
|
-
return {
|
|
53
|
-
summary: 'Register new user',
|
|
54
|
-
tags: ['Users']
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// ⚠️ WAJIB! TypeScript akan error kalau tidak ada
|
|
59
|
-
async handler(ctx: Context) {
|
|
60
|
-
const { email } = ctx.body;
|
|
61
|
-
return { success: true, email };
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const app = createApp();
|
|
66
|
-
app.post(new UserRegister());
|
|
67
|
-
app.listen(3000);
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### Menggunakan `RouteBase` Interface (Flexible)
|
|
71
|
-
|
|
72
|
-
Gunakan interface `RouteBase` untuk fleksibilitas (handler optional untuk file-based routing):
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
import { createApp, RouteBase, Context } from 'nexus';
|
|
76
|
-
import { z } from 'zod';
|
|
77
|
-
|
|
78
|
-
class UserRegister implements RouteBase {
|
|
79
|
-
pathName = '/api/users/register';
|
|
80
|
-
|
|
81
|
-
async handler(ctx: Context) {
|
|
82
|
-
const { email } = ctx.body;
|
|
83
|
-
return { success: true, email };
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
app.post(new UserRegister());
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Route Abstract Class (Recommended)
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
abstract class Route<TContext = Context> {
|
|
94
|
-
/** REQUIRED: Path route (e.g., '/api/users/:id') */
|
|
95
|
-
abstract pathName: string;
|
|
96
|
-
|
|
97
|
-
/** REQUIRED: Handler function - TypeScript enforced! */
|
|
98
|
-
abstract handler(ctx: TContext): Promise<any> | any;
|
|
99
|
-
|
|
100
|
-
/** Optional: HTTP method */
|
|
101
|
-
method?: HTTPMethod | HTTPMethod[];
|
|
102
|
-
|
|
103
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
104
|
-
// Helper Methods
|
|
105
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
/** Helper: Return success response */
|
|
108
|
-
protected ok<T>(data: T): { success: true } & T;
|
|
109
|
-
|
|
110
|
-
/** Helper: Return error response with status code */
|
|
111
|
-
protected fail(ctx: TContext, status: number, message: string, data?: any): Response;
|
|
112
|
-
|
|
113
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
114
|
-
// Lifecycle Hooks
|
|
115
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
116
|
-
|
|
117
|
-
/** Optional: Hook sebelum handler */
|
|
118
|
-
onBefore?(ctx: TContext): Promise<any | void> | any | void;
|
|
119
|
-
|
|
120
|
-
/** Optional: Hook setelah handler sukses */
|
|
121
|
-
onAfter?(ctx: TContext, result: any): Promise<any> | any;
|
|
122
|
-
|
|
123
|
-
/** Optional: Custom error handling */
|
|
124
|
-
onError?(ctx: TContext, error: Error): Promise<any> | any;
|
|
125
|
-
|
|
126
|
-
/** Optional: Schema validation (Zod) */
|
|
127
|
-
schema?(): SchemaConfig;
|
|
128
|
-
|
|
129
|
-
/** Optional: Metadata untuk Swagger/OpenAPI */
|
|
130
|
-
meta?(): RouteMeta;
|
|
131
|
-
|
|
132
|
-
/** Optional: Route-specific middlewares */
|
|
133
|
-
middlewares?(): Middleware[];
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
## Helper Methods
|
|
138
|
-
|
|
139
|
-
Route abstract class menyediakan helper methods untuk response handling yang lebih clean:
|
|
140
|
-
|
|
141
|
-
### `ok(data)` - Success Response
|
|
142
|
-
|
|
143
|
-
Mengembalikan response sukses dengan `success: true`:
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
class GetUser extends Route {
|
|
147
|
-
pathName = '/api/users/:id';
|
|
148
|
-
|
|
149
|
-
async handler(ctx: Context) {
|
|
150
|
-
const user = await db.findUser(ctx.params.id);
|
|
151
|
-
|
|
152
|
-
// ✅ Clean dan konsisten!
|
|
153
|
-
return this.ok({ user });
|
|
154
|
-
// Returns: { success: true, user: { id: 1, name: 'John' } }
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### `fail(ctx, status, message, data?)` - Error Response
|
|
160
|
-
|
|
161
|
-
Mengembalikan error response dengan status code:
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
class GetUser extends Route {
|
|
165
|
-
pathName = '/api/users/:id';
|
|
166
|
-
|
|
167
|
-
async handler(ctx: Context) {
|
|
168
|
-
const user = await db.findUser(ctx.params.id);
|
|
169
|
-
|
|
170
|
-
if (!user) {
|
|
171
|
-
// ✅ Returns 404 dengan body: { success: false, message: 'User not found' }
|
|
172
|
-
return this.fail(ctx, 404, 'User not found');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return this.ok({ user });
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
class CreateUser extends Route {
|
|
180
|
-
pathName = '/api/users';
|
|
181
|
-
|
|
182
|
-
async handler(ctx: Context) {
|
|
183
|
-
const errors = validate(ctx.body);
|
|
184
|
-
|
|
185
|
-
if (errors.length > 0) {
|
|
186
|
-
// ✅ Returns 400 dengan body: { success: false, message: 'Validation failed', data: { errors: [...] } }
|
|
187
|
-
return this.fail(ctx, 400, 'Validation failed', { errors });
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const user = await db.createUser(ctx.body);
|
|
191
|
-
return this.ok({ user });
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### Kombinasi dengan Lifecycle Hooks
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
class ProtectedResource extends Route {
|
|
200
|
-
pathName = '/api/admin/settings';
|
|
201
|
-
|
|
202
|
-
async onBefore(ctx: Context) {
|
|
203
|
-
if (!ctx.headers.authorization) {
|
|
204
|
-
return this.fail(ctx, 401, 'Unauthorized');
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async onError(ctx: Context, error: Error) {
|
|
209
|
-
return this.fail(ctx, 500, error.message);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async handler(ctx: Context) {
|
|
213
|
-
const settings = await db.getSettings();
|
|
214
|
-
return this.ok({ settings });
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
## Lifecycle Hooks
|
|
220
|
-
|
|
221
|
-
### `onBefore` - Pre-handler Hook
|
|
222
|
-
|
|
223
|
-
Dijalankan **sebelum handler**. Gunakan untuk:
|
|
224
|
-
- Logging request
|
|
225
|
-
- Authentication check
|
|
226
|
-
- Early return / redirect
|
|
227
|
-
|
|
228
|
-
```typescript
|
|
229
|
-
class ProtectedRoute extends Route {
|
|
230
|
-
pathName = '/api/admin/dashboard';
|
|
231
|
-
|
|
232
|
-
async onBefore(ctx: Context) {
|
|
233
|
-
// Check auth
|
|
234
|
-
if (!ctx.headers.authorization) {
|
|
235
|
-
// Return value = skip handler, langsung return response ini
|
|
236
|
-
return {
|
|
237
|
-
error: 'Unauthorized',
|
|
238
|
-
code: 401
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Return undefined = lanjut ke handler
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
async handler(ctx: Context) {
|
|
246
|
-
return { data: 'Admin dashboard data' };
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
### `onAfter` - Post-handler Hook
|
|
252
|
-
|
|
253
|
-
Dijalankan **setelah handler sukses**. Gunakan untuk:
|
|
254
|
-
- Transform response
|
|
255
|
-
- Add metadata
|
|
256
|
-
- Logging response
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
class ApiRoute extends Route {
|
|
260
|
-
pathName = '/api/users';
|
|
261
|
-
|
|
262
|
-
async handler(ctx: Context) {
|
|
263
|
-
return { users: [{ id: 1, name: 'John' }] };
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async onAfter(ctx: Context, result: any) {
|
|
267
|
-
// Transform response
|
|
268
|
-
return {
|
|
269
|
-
...result,
|
|
270
|
-
meta: {
|
|
271
|
-
timestamp: Date.now(),
|
|
272
|
-
version: '1.0.0'
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
### `onError` - Error Handler Hook
|
|
280
|
-
|
|
281
|
-
Dijalankan **saat handler atau onBefore throw error**. Gunakan untuk:
|
|
282
|
-
- Custom error format
|
|
283
|
-
- Error logging
|
|
284
|
-
- Fallback response
|
|
285
|
-
|
|
286
|
-
```typescript
|
|
287
|
-
class SafeRoute extends Route {
|
|
288
|
-
pathName = '/api/risky';
|
|
289
|
-
|
|
290
|
-
async handler(ctx: Context) {
|
|
291
|
-
throw new Error('Something went wrong!');
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
async onError(ctx: Context, error: Error) {
|
|
295
|
-
console.error('Logged error:', error);
|
|
296
|
-
|
|
297
|
-
return {
|
|
298
|
-
success: false,
|
|
299
|
-
error: error.message,
|
|
300
|
-
code: 'INTERNAL_ERROR',
|
|
301
|
-
timestamp: Date.now()
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
## RouteBase Interface (Legacy)
|
|
308
|
-
|
|
309
|
-
```typescript
|
|
310
|
-
interface RouteBase<TContext = Context> {
|
|
311
|
-
/** Path route (e.g., '/api/users/:id') */
|
|
312
|
-
pathName?: string;
|
|
313
|
-
|
|
314
|
-
/** Optional: Schema validation (Zod) */
|
|
315
|
-
schema?: () => SchemaConfig;
|
|
316
|
-
|
|
317
|
-
/** Optional: Metadata untuk Swagger/OpenAPI */
|
|
318
|
-
meta?: () => RouteMeta;
|
|
319
|
-
|
|
320
|
-
/** Optional: Route-specific middlewares */
|
|
321
|
-
middlewares?: () => Middleware[];
|
|
322
|
-
|
|
323
|
-
/** Handler function (optional for file-based routing) */
|
|
324
|
-
handler?: Handler<TContext>;
|
|
325
|
-
}
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
## Penggunaan
|
|
329
|
-
|
|
330
|
-
### 1. Basic Route
|
|
331
|
-
|
|
332
|
-
```typescript
|
|
333
|
-
class HealthCheck implements RouteBase {
|
|
334
|
-
pathName = '/health';
|
|
335
|
-
|
|
336
|
-
async handler(ctx: Context) {
|
|
337
|
-
return { status: 'ok', timestamp: new Date().toISOString() };
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
app.get(new HealthCheck());
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
### 2. Route dengan Validasi
|
|
345
|
-
|
|
346
|
-
```typescript
|
|
347
|
-
class CreateProduct implements RouteBase {
|
|
348
|
-
pathName = '/api/products';
|
|
349
|
-
|
|
350
|
-
schema() {
|
|
351
|
-
return {
|
|
352
|
-
body: z.object({
|
|
353
|
-
name: z.string().min(3, 'Nama minimal 3 karakter'),
|
|
354
|
-
price: z.number().positive('Harga harus positif'),
|
|
355
|
-
category: z.enum(['electronics', 'clothing', 'food'])
|
|
356
|
-
})
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
async handler(ctx: Context) {
|
|
361
|
-
const { name, price, category } = ctx.body;
|
|
362
|
-
// Save to database...
|
|
363
|
-
return { id: 'prod_123', name, price, category };
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
app.post(new CreateProduct());
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
### 3. Route dengan Path Parameters
|
|
371
|
-
|
|
372
|
-
```typescript
|
|
373
|
-
class GetUserById implements RouteBase {
|
|
374
|
-
pathName = '/api/users/:id';
|
|
375
|
-
|
|
376
|
-
schema() {
|
|
377
|
-
return {
|
|
378
|
-
params: z.object({
|
|
379
|
-
id: z.string().uuid('ID harus valid UUID')
|
|
380
|
-
})
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
meta() {
|
|
385
|
-
return {
|
|
386
|
-
summary: 'Get user by ID',
|
|
387
|
-
description: 'Retrieve detailed user information',
|
|
388
|
-
tags: ['Users'],
|
|
389
|
-
responses: {
|
|
390
|
-
200: 'User found',
|
|
391
|
-
404: 'User not found'
|
|
392
|
-
}
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
async handler(ctx: Context) {
|
|
397
|
-
const { id } = ctx.params;
|
|
398
|
-
// Fetch from database...
|
|
399
|
-
return { id, name: 'John Doe', email: 'john@example.com' };
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
app.get(new GetUserById());
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### 4. Route dengan Query Parameters
|
|
407
|
-
|
|
408
|
-
```typescript
|
|
409
|
-
class ListProducts implements RouteBase {
|
|
410
|
-
pathName = '/api/products';
|
|
411
|
-
|
|
412
|
-
schema() {
|
|
413
|
-
return {
|
|
414
|
-
query: z.object({
|
|
415
|
-
page: z.coerce.number().min(1).default(1),
|
|
416
|
-
limit: z.coerce.number().min(1).max(100).default(10),
|
|
417
|
-
category: z.string().optional(),
|
|
418
|
-
search: z.string().optional()
|
|
419
|
-
})
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
meta() {
|
|
424
|
-
return {
|
|
425
|
-
summary: 'List products',
|
|
426
|
-
description: 'Get paginated list of products with optional filters',
|
|
427
|
-
tags: ['Products']
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
async handler(ctx: Context) {
|
|
432
|
-
const { page, limit, category, search } = ctx.query;
|
|
433
|
-
// Fetch from database with filters...
|
|
434
|
-
return {
|
|
435
|
-
data: [],
|
|
436
|
-
pagination: { page, limit, total: 0 }
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
app.get(new ListProducts());
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
### 5. Route dengan Middleware
|
|
445
|
-
|
|
446
|
-
```typescript
|
|
447
|
-
import { authMiddleware, rateLimitMiddleware } from './middlewares';
|
|
448
|
-
|
|
449
|
-
class DeleteUser implements RouteBase {
|
|
450
|
-
pathName = '/api/users/:id';
|
|
451
|
-
|
|
452
|
-
middlewares() {
|
|
453
|
-
return [
|
|
454
|
-
authMiddleware({ role: 'admin' }),
|
|
455
|
-
rateLimitMiddleware({ max: 10, window: '1m' })
|
|
456
|
-
];
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
schema() {
|
|
460
|
-
return {
|
|
461
|
-
params: z.object({
|
|
462
|
-
id: z.string().uuid()
|
|
463
|
-
})
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
meta() {
|
|
468
|
-
return {
|
|
469
|
-
summary: 'Delete user',
|
|
470
|
-
tags: ['Users', 'Admin'],
|
|
471
|
-
responses: {
|
|
472
|
-
204: 'User deleted',
|
|
473
|
-
401: 'Unauthorized',
|
|
474
|
-
403: 'Forbidden',
|
|
475
|
-
404: 'User not found'
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
async handler(ctx: Context) {
|
|
481
|
-
const { id } = ctx.params;
|
|
482
|
-
// Delete from database...
|
|
483
|
-
return ctx.response.status(204).text('');
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
app.delete(new DeleteUser());
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
### 6. Route dengan Custom Validation Error
|
|
491
|
-
|
|
492
|
-
```typescript
|
|
493
|
-
class UpdateProfile implements RouteBase {
|
|
494
|
-
pathName = '/api/profile';
|
|
495
|
-
|
|
496
|
-
schema() {
|
|
497
|
-
return {
|
|
498
|
-
body: z.object({
|
|
499
|
-
name: z.string().min(2),
|
|
500
|
-
bio: z.string().max(500).optional(),
|
|
501
|
-
avatar: z.string().url().optional()
|
|
502
|
-
}),
|
|
503
|
-
onValidationError: (errors, ctx) => ({
|
|
504
|
-
statusCode: 422,
|
|
505
|
-
headers: { 'Content-Type': 'application/json' },
|
|
506
|
-
body: JSON.stringify({
|
|
507
|
-
code: 'VALIDATION_ERROR',
|
|
508
|
-
message: 'Data tidak valid',
|
|
509
|
-
errors: errors.map(e => ({
|
|
510
|
-
field: e.path.join('.'),
|
|
511
|
-
message: e.message
|
|
512
|
-
}))
|
|
513
|
-
})
|
|
514
|
-
})
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
async handler(ctx: Context) {
|
|
519
|
-
// Update profile...
|
|
520
|
-
return { success: true };
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
app.put(new UpdateProfile());
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
## Organisasi File
|
|
528
|
-
|
|
529
|
-
Rekomendasi struktur folder untuk class-based routes:
|
|
530
|
-
|
|
531
|
-
```
|
|
532
|
-
src/
|
|
533
|
-
├── routes/
|
|
534
|
-
│ ├── index.ts # Export semua routes
|
|
535
|
-
│ ├── auth/
|
|
536
|
-
│ │ ├── login.ts
|
|
537
|
-
│ │ ├── register.ts
|
|
538
|
-
│ │ └── logout.ts
|
|
539
|
-
│ ├── users/
|
|
540
|
-
│ │ ├── list.ts
|
|
541
|
-
│ │ ├── get.ts
|
|
542
|
-
│ │ ├── create.ts
|
|
543
|
-
│ │ ├── update.ts
|
|
544
|
-
│ │ └── delete.ts
|
|
545
|
-
│ └── products/
|
|
546
|
-
│ ├── list.ts
|
|
547
|
-
│ ├── get.ts
|
|
548
|
-
│ └── create.ts
|
|
549
|
-
├── middlewares/
|
|
550
|
-
├── services/
|
|
551
|
-
└── app.ts
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
### Contoh: routes/auth/register.ts
|
|
555
|
-
|
|
556
|
-
```typescript
|
|
557
|
-
import { RouteBase, Context } from 'nexus';
|
|
558
|
-
import { z } from 'zod';
|
|
559
|
-
import { UserService } from '../../services/user';
|
|
560
|
-
|
|
561
|
-
export class RegisterRoute implements RouteBase {
|
|
562
|
-
pathName = '/api/auth/register';
|
|
563
|
-
|
|
564
|
-
private userService: UserService;
|
|
565
|
-
|
|
566
|
-
constructor(userService?: UserService) {
|
|
567
|
-
this.userService = userService || new UserService();
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
schema() {
|
|
571
|
-
return {
|
|
572
|
-
body: z.object({
|
|
573
|
-
email: z.string().email(),
|
|
574
|
-
username: z.string().min(3).max(20),
|
|
575
|
-
password: z.string().min(8)
|
|
576
|
-
})
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
meta() {
|
|
581
|
-
return {
|
|
582
|
-
summary: 'Register new user',
|
|
583
|
-
tags: ['Authentication'],
|
|
584
|
-
responses: {
|
|
585
|
-
201: 'User created',
|
|
586
|
-
400: 'Invalid data',
|
|
587
|
-
409: 'Email already exists'
|
|
588
|
-
}
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
async handler(ctx: Context) {
|
|
593
|
-
const { email, username, password } = ctx.body;
|
|
594
|
-
|
|
595
|
-
const user = await this.userService.create({ email, username, password });
|
|
596
|
-
|
|
597
|
-
return ctx.response.status(201).json({
|
|
598
|
-
success: true,
|
|
599
|
-
user: { id: user.id, email: user.email, username: user.username }
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
### Contoh: routes/index.ts
|
|
606
|
-
|
|
607
|
-
```typescript
|
|
608
|
-
import { Application } from 'nexus';
|
|
609
|
-
|
|
610
|
-
// Auth routes
|
|
611
|
-
import { LoginRoute } from './auth/login';
|
|
612
|
-
import { RegisterRoute } from './auth/register';
|
|
613
|
-
import { LogoutRoute } from './auth/logout';
|
|
614
|
-
|
|
615
|
-
// User routes
|
|
616
|
-
import { ListUsersRoute } from './users/list';
|
|
617
|
-
import { GetUserRoute } from './users/get';
|
|
618
|
-
import { CreateUserRoute } from './users/create';
|
|
619
|
-
import { UpdateUserRoute } from './users/update';
|
|
620
|
-
import { DeleteUserRoute } from './users/delete';
|
|
621
|
-
|
|
622
|
-
export function registerRoutes(app: Application) {
|
|
623
|
-
// Auth
|
|
624
|
-
app.post(new LoginRoute());
|
|
625
|
-
app.post(new RegisterRoute());
|
|
626
|
-
app.post(new LogoutRoute());
|
|
627
|
-
|
|
628
|
-
// Users
|
|
629
|
-
app.get(new ListUsersRoute());
|
|
630
|
-
app.get(new GetUserRoute());
|
|
631
|
-
app.post(new CreateUserRoute());
|
|
632
|
-
app.put(new UpdateUserRoute());
|
|
633
|
-
app.delete(new DeleteUserRoute());
|
|
634
|
-
}
|
|
635
|
-
```
|
|
636
|
-
|
|
637
|
-
### Contoh: app.ts
|
|
638
|
-
|
|
639
|
-
```typescript
|
|
640
|
-
import { createApp, swagger, postman, playground } from 'nexus';
|
|
641
|
-
import { registerRoutes } from './routes';
|
|
642
|
-
|
|
643
|
-
const app = createApp({ debug: true });
|
|
644
|
-
|
|
645
|
-
// Plugins
|
|
646
|
-
app.plugin(swagger());
|
|
647
|
-
app.plugin(postman());
|
|
648
|
-
app.plugin(playground());
|
|
649
|
-
|
|
650
|
-
// Register all routes
|
|
651
|
-
registerRoutes(app);
|
|
652
|
-
|
|
653
|
-
app.listen(3000, () => {
|
|
654
|
-
console.log('🚀 Server running at http://localhost:3000');
|
|
655
|
-
});
|
|
656
|
-
```
|
|
657
|
-
|
|
658
|
-
## Perbandingan: Function vs Class-Based
|
|
659
|
-
|
|
660
|
-
### Function-Based (Inline)
|
|
661
|
-
|
|
662
|
-
```typescript
|
|
663
|
-
app.post('/api/users', {
|
|
664
|
-
schema: {
|
|
665
|
-
body: z.object({ email: z.string().email() })
|
|
666
|
-
},
|
|
667
|
-
handler: async (ctx) => {
|
|
668
|
-
return { success: true };
|
|
669
|
-
},
|
|
670
|
-
meta: {
|
|
671
|
-
summary: 'Create user',
|
|
672
|
-
tags: ['Users']
|
|
673
|
-
}
|
|
674
|
-
});
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
### Class-Based
|
|
678
|
-
|
|
679
|
-
```typescript
|
|
680
|
-
class CreateUser implements RouteBase {
|
|
681
|
-
pathName = '/api/users';
|
|
682
|
-
|
|
683
|
-
schema() {
|
|
684
|
-
return { body: z.object({ email: z.string().email() }) };
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
meta() {
|
|
688
|
-
return { summary: 'Create user', tags: ['Users'] };
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
async handler(ctx: Context) {
|
|
692
|
-
return { success: true };
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
app.post(new CreateUser());
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
## Tips & Best Practices
|
|
700
|
-
|
|
701
|
-
### 1. Gunakan Dependency Injection
|
|
702
|
-
|
|
703
|
-
```typescript
|
|
704
|
-
class UserController implements RouteBase {
|
|
705
|
-
pathName = '/api/users';
|
|
706
|
-
|
|
707
|
-
constructor(
|
|
708
|
-
private userService: UserService,
|
|
709
|
-
private logger: Logger
|
|
710
|
-
) {}
|
|
711
|
-
|
|
712
|
-
async handler(ctx: Context) {
|
|
713
|
-
this.logger.info('Fetching users');
|
|
714
|
-
const users = await this.userService.findAll();
|
|
715
|
-
return { users };
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
// Dengan DI container
|
|
720
|
-
const userService = container.resolve(UserService);
|
|
721
|
-
const logger = container.resolve(Logger);
|
|
722
|
-
app.get(new UserController(userService, logger));
|
|
723
|
-
```
|
|
724
|
-
|
|
725
|
-
### 2. Extend Base Class untuk Shared Logic
|
|
726
|
-
|
|
727
|
-
```typescript
|
|
728
|
-
abstract class BaseRoute implements RouteBase {
|
|
729
|
-
abstract pathName: string;
|
|
730
|
-
abstract handler(ctx: Context): Promise<any>;
|
|
731
|
-
|
|
732
|
-
// Shared validation error handler
|
|
733
|
-
protected validationErrorHandler(errors: any[], ctx: Context) {
|
|
734
|
-
return {
|
|
735
|
-
statusCode: 422,
|
|
736
|
-
headers: { 'Content-Type': 'application/json' },
|
|
737
|
-
body: JSON.stringify({ code: 'VALIDATION_ERROR', errors })
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
schema() {
|
|
742
|
-
return {
|
|
743
|
-
onValidationError: this.validationErrorHandler.bind(this)
|
|
744
|
-
};
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
class MyRoute extends BaseRoute {
|
|
749
|
-
pathName = '/api/example';
|
|
750
|
-
|
|
751
|
-
schema() {
|
|
752
|
-
return {
|
|
753
|
-
...super.schema(),
|
|
754
|
-
body: z.object({ name: z.string() })
|
|
755
|
-
};
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
async handler(ctx: Context) {
|
|
759
|
-
return { hello: ctx.body.name };
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
```
|
|
763
|
-
|
|
764
|
-
### 3. Testing
|
|
765
|
-
|
|
766
|
-
```typescript
|
|
767
|
-
import { TestClient } from 'nexus';
|
|
768
|
-
|
|
769
|
-
describe('RegisterRoute', () => {
|
|
770
|
-
let app: Application;
|
|
771
|
-
let client: TestClient;
|
|
772
|
-
|
|
773
|
-
beforeEach(() => {
|
|
774
|
-
app = createApp();
|
|
775
|
-
app.post(new RegisterRoute());
|
|
776
|
-
client = new TestClient(app);
|
|
777
|
-
});
|
|
778
|
-
|
|
779
|
-
it('should register user successfully', async () => {
|
|
780
|
-
const res = await client.post('/api/auth/register', {
|
|
781
|
-
body: {
|
|
782
|
-
email: 'test@example.com',
|
|
783
|
-
username: 'testuser',
|
|
784
|
-
password: 'password123'
|
|
785
|
-
}
|
|
786
|
-
});
|
|
787
|
-
|
|
788
|
-
expect(res.status).toBe(201);
|
|
789
|
-
expect(res.body.success).toBe(true);
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
it('should return validation error for invalid email', async () => {
|
|
793
|
-
const res = await client.post('/api/auth/register', {
|
|
794
|
-
body: {
|
|
795
|
-
email: 'invalid-email',
|
|
796
|
-
username: 'testuser',
|
|
797
|
-
password: 'password123'
|
|
798
|
-
}
|
|
799
|
-
});
|
|
800
|
-
|
|
801
|
-
expect(res.status).toBe(422);
|
|
802
|
-
expect(res.body.code).toBe('VALIDATION_ERROR');
|
|
803
|
-
});
|
|
804
|
-
});
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
## HTTP Methods yang Didukung
|
|
808
|
-
|
|
809
|
-
| Method | Usage |
|
|
810
|
-
|--------|-------|
|
|
811
|
-
| `app.get(route)` | GET request |
|
|
812
|
-
| `app.post(route)` | POST request |
|
|
813
|
-
| `app.put(route)` | PUT request |
|
|
814
|
-
| `app.patch(route)` | PATCH request |
|
|
815
|
-
| `app.delete(route)` | DELETE request |
|
|
816
|
-
|
|
817
|
-
## Integrasi dengan Features
|
|
818
|
-
|
|
819
|
-
Class-based routes **otomatis terintegrasi** dengan:
|
|
820
|
-
|
|
821
|
-
- 📚 **Swagger** - Dokumentasi API otomatis dari `meta()`
|
|
822
|
-
- 📦 **Postman** - Collection generator
|
|
823
|
-
- 🎮 **Playground** - Interactive API explorer
|
|
824
|
-
- ✅ **Validation** - Zod schema dari `schema()`
|
|
825
|
-
|
|
826
|
-
```typescript
|
|
827
|
-
const app = createApp();
|
|
828
|
-
|
|
829
|
-
app.plugin(swagger());
|
|
830
|
-
app.plugin(postman());
|
|
831
|
-
app.plugin(playground());
|
|
832
|
-
|
|
833
|
-
// Routes akan muncul di Swagger, Postman, dan Playground
|
|
834
|
-
app.post(new RegisterRoute());
|
|
835
|
-
app.get(new ListUsersRoute());
|
|
836
|
-
```
|
|
837
|
-
|
|
838
|
-
---
|
|
839
|
-
|
|
840
|
-
**Next:** [20-testing.md](./20-testing.md) - Testing your routes
|