@engjts/nexus 0.1.8 → 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.
Files changed (205) hide show
  1. package/package.json +1 -1
  2. package/BENCHMARK_REPORT.md +0 -343
  3. package/documentation/01-getting-started.md +0 -240
  4. package/documentation/02-context.md +0 -335
  5. package/documentation/03-routing.md +0 -397
  6. package/documentation/04-middleware.md +0 -483
  7. package/documentation/05-validation.md +0 -514
  8. package/documentation/06-error-handling.md +0 -465
  9. package/documentation/07-performance.md +0 -364
  10. package/documentation/08-adapters.md +0 -470
  11. package/documentation/09-api-reference.md +0 -548
  12. package/documentation/10-examples.md +0 -582
  13. package/documentation/11-deployment.md +0 -477
  14. package/documentation/12-sentry.md +0 -620
  15. package/documentation/13-sentry-data-storage.md +0 -996
  16. package/documentation/14-sentry-data-reference.md +0 -457
  17. package/documentation/15-sentry-summary.md +0 -409
  18. package/documentation/16-alerts-system.md +0 -745
  19. package/documentation/17-alert-adapters.md +0 -696
  20. package/documentation/18-alerts-implementation-summary.md +0 -385
  21. package/documentation/19-class-based-routing.md +0 -840
  22. package/documentation/20-websocket-realtime.md +0 -813
  23. package/documentation/21-cache-system.md +0 -510
  24. package/documentation/22-job-queue.md +0 -772
  25. package/documentation/23-sentry-plugin.md +0 -551
  26. package/documentation/24-testing-utilities.md +0 -1287
  27. package/documentation/25-api-versioning.md +0 -533
  28. package/documentation/26-context-store.md +0 -607
  29. package/documentation/27-dependency-injection.md +0 -329
  30. package/documentation/28-lifecycle-hooks.md +0 -521
  31. package/documentation/29-package-structure.md +0 -196
  32. package/documentation/30-plugin-system.md +0 -414
  33. package/documentation/31-jwt-authentication.md +0 -597
  34. package/documentation/32-cli.md +0 -268
  35. package/documentation/ALERTS-COMPLETE-SUMMARY.md +0 -429
  36. package/documentation/ALERTS-INDEX.md +0 -330
  37. package/documentation/ALERTS-QUICK-REFERENCE.md +0 -286
  38. package/documentation/README.md +0 -178
  39. package/documentation/index.html +0 -34
  40. package/modern_framework_paper.md +0 -1870
  41. package/public/css/style.css +0 -87
  42. package/public/index.html +0 -34
  43. package/public/js/app.js +0 -27
  44. package/src/advanced/cache/InMemoryCacheStore.ts +0 -68
  45. package/src/advanced/cache/MultiTierCache.ts +0 -194
  46. package/src/advanced/cache/RedisCacheStore.ts +0 -341
  47. package/src/advanced/cache/index.ts +0 -5
  48. package/src/advanced/cache/types.ts +0 -40
  49. package/src/advanced/graphql/SimpleDataLoader.ts +0 -42
  50. package/src/advanced/graphql/index.ts +0 -22
  51. package/src/advanced/graphql/server.ts +0 -252
  52. package/src/advanced/graphql/types.ts +0 -42
  53. package/src/advanced/jobs/InMemoryQueueStore.ts +0 -68
  54. package/src/advanced/jobs/JobQueue.ts +0 -556
  55. package/src/advanced/jobs/RedisQueueStore.ts +0 -367
  56. package/src/advanced/jobs/index.ts +0 -5
  57. package/src/advanced/jobs/types.ts +0 -70
  58. package/src/advanced/observability/APMManager.ts +0 -163
  59. package/src/advanced/observability/AlertManager.ts +0 -109
  60. package/src/advanced/observability/MetricRegistry.ts +0 -151
  61. package/src/advanced/observability/ObservabilityCenter.ts +0 -304
  62. package/src/advanced/observability/StructuredLogger.ts +0 -154
  63. package/src/advanced/observability/TracingManager.ts +0 -117
  64. package/src/advanced/observability/adapters.ts +0 -304
  65. package/src/advanced/observability/createObservabilityMiddleware.ts +0 -63
  66. package/src/advanced/observability/index.ts +0 -11
  67. package/src/advanced/observability/types.ts +0 -174
  68. package/src/advanced/playground/extractPathParams.ts +0 -6
  69. package/src/advanced/playground/generateFieldExample.ts +0 -31
  70. package/src/advanced/playground/generatePlaygroundHTML.ts +0 -1956
  71. package/src/advanced/playground/generateSummary.ts +0 -19
  72. package/src/advanced/playground/getTagFromPath.ts +0 -9
  73. package/src/advanced/playground/index.ts +0 -8
  74. package/src/advanced/playground/playground.ts +0 -250
  75. package/src/advanced/playground/types.ts +0 -49
  76. package/src/advanced/playground/zodToExample.ts +0 -16
  77. package/src/advanced/playground/zodToParams.ts +0 -15
  78. package/src/advanced/postman/buildAuth.ts +0 -31
  79. package/src/advanced/postman/buildBody.ts +0 -15
  80. package/src/advanced/postman/buildQueryParams.ts +0 -27
  81. package/src/advanced/postman/buildRequestItem.ts +0 -36
  82. package/src/advanced/postman/buildResponses.ts +0 -11
  83. package/src/advanced/postman/buildUrl.ts +0 -33
  84. package/src/advanced/postman/capitalize.ts +0 -4
  85. package/src/advanced/postman/generateCollection.ts +0 -59
  86. package/src/advanced/postman/generateEnvironment.ts +0 -34
  87. package/src/advanced/postman/generateExampleFromZod.ts +0 -21
  88. package/src/advanced/postman/generateFieldExample.ts +0 -45
  89. package/src/advanced/postman/generateName.ts +0 -20
  90. package/src/advanced/postman/generateUUID.ts +0 -11
  91. package/src/advanced/postman/getTagFromPath.ts +0 -10
  92. package/src/advanced/postman/index.ts +0 -28
  93. package/src/advanced/postman/postman.ts +0 -156
  94. package/src/advanced/postman/slugify.ts +0 -7
  95. package/src/advanced/postman/types.ts +0 -140
  96. package/src/advanced/realtime/index.ts +0 -18
  97. package/src/advanced/realtime/websocket.ts +0 -231
  98. package/src/advanced/sentry/index.ts +0 -1236
  99. package/src/advanced/sentry/types.ts +0 -355
  100. package/src/advanced/static/generateDirectoryListing.ts +0 -47
  101. package/src/advanced/static/generateETag.ts +0 -7
  102. package/src/advanced/static/getMimeType.ts +0 -9
  103. package/src/advanced/static/index.ts +0 -32
  104. package/src/advanced/static/isSafePath.ts +0 -13
  105. package/src/advanced/static/publicDir.ts +0 -21
  106. package/src/advanced/static/serveStatic.ts +0 -225
  107. package/src/advanced/static/spa.ts +0 -24
  108. package/src/advanced/static/types.ts +0 -159
  109. package/src/advanced/swagger/SwaggerGenerator.ts +0 -66
  110. package/src/advanced/swagger/buildOperation.ts +0 -61
  111. package/src/advanced/swagger/buildParameters.ts +0 -61
  112. package/src/advanced/swagger/buildRequestBody.ts +0 -21
  113. package/src/advanced/swagger/buildResponses.ts +0 -54
  114. package/src/advanced/swagger/capitalize.ts +0 -5
  115. package/src/advanced/swagger/convertPath.ts +0 -9
  116. package/src/advanced/swagger/createSwagger.ts +0 -12
  117. package/src/advanced/swagger/generateOperationId.ts +0 -21
  118. package/src/advanced/swagger/generateSpec.ts +0 -105
  119. package/src/advanced/swagger/generateSummary.ts +0 -24
  120. package/src/advanced/swagger/generateSwaggerUI.ts +0 -70
  121. package/src/advanced/swagger/generateThemeCss.ts +0 -53
  122. package/src/advanced/swagger/index.ts +0 -25
  123. package/src/advanced/swagger/swagger.ts +0 -237
  124. package/src/advanced/swagger/types.ts +0 -206
  125. package/src/advanced/swagger/zodFieldToOpenAPI.ts +0 -94
  126. package/src/advanced/swagger/zodSchemaToOpenAPI.ts +0 -50
  127. package/src/advanced/swagger/zodToOpenAPI.ts +0 -22
  128. package/src/advanced/testing/factory.ts +0 -509
  129. package/src/advanced/testing/harness.ts +0 -612
  130. package/src/advanced/testing/index.ts +0 -430
  131. package/src/advanced/testing/load-test.ts +0 -618
  132. package/src/advanced/testing/mock-server.ts +0 -498
  133. package/src/advanced/testing/mock.ts +0 -670
  134. package/src/cli/bin.ts +0 -9
  135. package/src/cli/cli.ts +0 -158
  136. package/src/cli/commands/add.ts +0 -178
  137. package/src/cli/commands/build.ts +0 -73
  138. package/src/cli/commands/create.ts +0 -166
  139. package/src/cli/commands/dev.ts +0 -85
  140. package/src/cli/commands/generate.ts +0 -99
  141. package/src/cli/commands/help.ts +0 -95
  142. package/src/cli/commands/init.ts +0 -91
  143. package/src/cli/commands/version.ts +0 -38
  144. package/src/cli/index.ts +0 -6
  145. package/src/cli/templates/generators.ts +0 -359
  146. package/src/cli/templates/index.ts +0 -680
  147. package/src/cli/utils/exec.ts +0 -52
  148. package/src/cli/utils/file-system.ts +0 -78
  149. package/src/cli/utils/logger.ts +0 -111
  150. package/src/core/adapter.ts +0 -88
  151. package/src/core/application.ts +0 -1453
  152. package/src/core/context-pool.ts +0 -79
  153. package/src/core/context.ts +0 -856
  154. package/src/core/index.ts +0 -94
  155. package/src/core/middleware.ts +0 -272
  156. package/src/core/performance/buffer-pool.ts +0 -108
  157. package/src/core/performance/middleware-optimizer.ts +0 -162
  158. package/src/core/plugin/PluginManager.ts +0 -435
  159. package/src/core/plugin/builder.ts +0 -358
  160. package/src/core/plugin/index.ts +0 -50
  161. package/src/core/plugin/types.ts +0 -214
  162. package/src/core/router/file-router.ts +0 -623
  163. package/src/core/router/index.ts +0 -260
  164. package/src/core/router/radix-tree.ts +0 -242
  165. package/src/core/serializer.ts +0 -397
  166. package/src/core/store/index.ts +0 -30
  167. package/src/core/store/registry.ts +0 -178
  168. package/src/core/store/request-store.ts +0 -240
  169. package/src/core/store/types.ts +0 -233
  170. package/src/core/types.ts +0 -616
  171. package/src/database/adapter.ts +0 -35
  172. package/src/database/adapters/index.ts +0 -1
  173. package/src/database/adapters/mysql.ts +0 -669
  174. package/src/database/database.ts +0 -70
  175. package/src/database/dialect.ts +0 -388
  176. package/src/database/index.ts +0 -12
  177. package/src/database/migrations.ts +0 -86
  178. package/src/database/optimizer.ts +0 -125
  179. package/src/database/query-builder.ts +0 -404
  180. package/src/database/realtime.ts +0 -53
  181. package/src/database/schema.ts +0 -71
  182. package/src/database/transactions.ts +0 -56
  183. package/src/database/types.ts +0 -87
  184. package/src/deployment/cluster.ts +0 -471
  185. package/src/deployment/config.ts +0 -454
  186. package/src/deployment/docker.ts +0 -599
  187. package/src/deployment/graceful-shutdown.ts +0 -373
  188. package/src/deployment/index.ts +0 -56
  189. package/src/index.ts +0 -281
  190. package/src/security/adapter.ts +0 -318
  191. package/src/security/auth/JWTPlugin.ts +0 -234
  192. package/src/security/auth/JWTProvider.ts +0 -316
  193. package/src/security/auth/adapter.ts +0 -12
  194. package/src/security/auth/jwt.ts +0 -234
  195. package/src/security/auth/middleware.ts +0 -188
  196. package/src/security/csrf.ts +0 -220
  197. package/src/security/headers.ts +0 -108
  198. package/src/security/index.ts +0 -60
  199. package/src/security/rate-limit/adapter.ts +0 -7
  200. package/src/security/rate-limit/memory.ts +0 -108
  201. package/src/security/rate-limit/middleware.ts +0 -181
  202. package/src/security/sanitization.ts +0 -75
  203. package/src/security/types.ts +0 -240
  204. package/src/security/utils.ts +0 -52
  205. package/tsconfig.json +0 -39
@@ -1,813 +0,0 @@
1
- # WebSocket Realtime
2
-
3
- Nexus Framework menyediakan **WebSocket support** built-in untuk membangun aplikasi realtime seperti chat, notifications, live updates, dan lainnya.
4
-
5
- ## Quick Start
6
-
7
- ```typescript
8
- import { createApp } from 'nexus';
9
-
10
- const app = createApp();
11
-
12
- // Define WebSocket route
13
- app.ws('/ws/chat', {
14
- onConnect: async (socket, ctx) => {
15
- console.log('Client connected');
16
- socket.send(JSON.stringify({ type: 'welcome', message: 'Hello!' }));
17
- },
18
-
19
- onMessage: async (socket, message, ctx) => {
20
- console.log('Received:', message);
21
- socket.send(JSON.stringify({ echo: message }));
22
- },
23
-
24
- onClose: async (socket, ctx) => {
25
- console.log('Client disconnected');
26
- }
27
- });
28
-
29
- app.listen(3000, () => {
30
- console.log('Server running at http://localhost:3000');
31
- console.log('WebSocket at ws://localhost:3000/ws/chat');
32
- });
33
- ```
34
-
35
- ## WebSocketRouteConfig Interface
36
-
37
- ```typescript
38
- interface WebSocketRouteConfig {
39
- /** Authentication handler - validate token and return user object */
40
- auth?: (ctx: WebSocketContext) => Promise<any>;
41
-
42
- /** Called before onConnect - can be used for additional setup */
43
- beforeConnect?: (socket: WebSocket, ctx: WebSocketContext) => Promise<void>;
44
-
45
- /** Called when client connects */
46
- onConnect?: (socket: WebSocket, ctx: WebSocketContext) => Promise<void>;
47
-
48
- /** Called when client sends a message */
49
- onMessage?: (socket: WebSocket, message: any, ctx: WebSocketContext) => Promise<void>;
50
-
51
- /** Called when client disconnects */
52
- onClose?: (socket: WebSocket, ctx: WebSocketContext, code: number, reason?: Buffer) => Promise<void>;
53
-
54
- /** Called on WebSocket error */
55
- onError?: (socket: WebSocket, error: Error, ctx: WebSocketContext) => Promise<void>;
56
-
57
- /** Enable room support (default: true) */
58
- rooms?: boolean;
59
- }
60
- ```
61
-
62
- ## WebSocketContext
63
-
64
- Context object yang tersedia di setiap handler:
65
-
66
- ```typescript
67
- interface WebSocketContext {
68
- /** WebSocket path (e.g., '/ws/chat') */
69
- path: string;
70
-
71
- /** Query parameters from URL */
72
- query: Record<string, string | string[]>;
73
-
74
- /** Request headers */
75
- headers: IncomingMessage['headers'];
76
-
77
- /** User object (set by auth handler) */
78
- user?: any;
79
-
80
- /** Custom metadata */
81
- metadata?: Record<string, any>;
82
-
83
- /** Raw Node.js request */
84
- raw: {
85
- req: IncomingMessage;
86
- };
87
- }
88
- ```
89
-
90
- ## Fitur-Fitur
91
-
92
- ### 1. Authentication
93
-
94
- Validasi token/credentials sebelum koneksi diterima:
95
-
96
- ```typescript
97
- app.ws('/ws/protected', {
98
- auth: async (ctx) => {
99
- const token = ctx.query.token as string;
100
-
101
- if (!token) {
102
- throw new Error('Token required');
103
- }
104
-
105
- // Verify JWT token
106
- const user = await verifyToken(token);
107
-
108
- if (!user) {
109
- throw new Error('Invalid token');
110
- }
111
-
112
- // Return user object - will be available as ctx.user
113
- return user;
114
- },
115
-
116
- onConnect: async (socket, ctx) => {
117
- // ctx.user is now available
118
- console.log(`User ${ctx.user.name} connected`);
119
-
120
- socket.send(JSON.stringify({
121
- type: 'authenticated',
122
- user: ctx.user
123
- }));
124
- },
125
-
126
- onMessage: async (socket, message, ctx) => {
127
- console.log(`Message from ${ctx.user.name}:`, message);
128
- }
129
- });
130
- ```
131
-
132
- **Client connection:**
133
- ```javascript
134
- const ws = new WebSocket('ws://localhost:3000/ws/protected?token=your-jwt-token');
135
- ```
136
-
137
- ### 2. Room Management
138
-
139
- Kelompokkan koneksi ke dalam "rooms" untuk broadcast targeted:
140
-
141
- ```typescript
142
- app.ws('/ws/chat', {
143
- onConnect: async (socket, ctx) => {
144
- const ws = app.getWebSocket()!;
145
- const room = ctx.query.room as string || 'general';
146
-
147
- // Join a room
148
- ws.joinRoom(room, socket);
149
-
150
- // Notify others in the room
151
- ws.broadcast(room, {
152
- type: 'user_joined',
153
- message: `New user joined ${room}`
154
- });
155
-
156
- socket.send(JSON.stringify({
157
- type: 'joined',
158
- room
159
- }));
160
- },
161
-
162
- onMessage: async (socket, message, ctx) => {
163
- const ws = app.getWebSocket()!;
164
-
165
- if (message.type === 'chat') {
166
- // Broadcast to current room
167
- ws.broadcast(message.room, {
168
- type: 'chat',
169
- user: ctx.user?.name,
170
- text: message.text,
171
- timestamp: new Date().toISOString()
172
- });
173
- }
174
-
175
- if (message.type === 'join_room') {
176
- ws.joinRoom(message.room, socket);
177
- socket.send(JSON.stringify({ type: 'room_joined', room: message.room }));
178
- }
179
-
180
- if (message.type === 'leave_room') {
181
- ws.leaveRoom(message.room, socket);
182
- socket.send(JSON.stringify({ type: 'room_left', room: message.room }));
183
- }
184
- },
185
-
186
- onClose: async (socket, ctx) => {
187
- const ws = app.getWebSocket()!;
188
-
189
- // Broadcast user left (rooms are auto-cleaned)
190
- ws.broadcast('general', {
191
- type: 'user_left',
192
- user: ctx.user?.name
193
- });
194
- }
195
- });
196
- ```
197
-
198
- ### 3. Broadcasting
199
-
200
- Kirim pesan ke semua client dalam room:
201
-
202
- ```typescript
203
- const ws = app.getWebSocket()!;
204
-
205
- // Broadcast to specific room
206
- ws.broadcast('notifications', {
207
- type: 'alert',
208
- message: 'System maintenance in 5 minutes'
209
- });
210
-
211
- // Broadcast to multiple rooms
212
- ['room1', 'room2', 'room3'].forEach(room => {
213
- ws.broadcast(room, { type: 'announcement', text: 'Hello everyone!' });
214
- });
215
- ```
216
-
217
- ### 4. Room Manager API
218
-
219
- ```typescript
220
- const ws = app.getWebSocket()!;
221
-
222
- // Create room explicitly
223
- ws.createRoom('vip-lounge');
224
-
225
- // Join room
226
- ws.joinRoom('vip-lounge', socket);
227
-
228
- // Leave room
229
- ws.leaveRoom('vip-lounge', socket);
230
-
231
- // Broadcast to room
232
- ws.broadcast('vip-lounge', { message: 'VIP announcement' });
233
-
234
- // Using roomManager helper
235
- ws.roomManager.create('new-room');
236
- ws.roomManager.join('new-room', socket);
237
- ws.roomManager.leave('new-room', socket);
238
- ws.roomManager.broadcast('new-room', { data: 'hello' });
239
- ws.roomManager.list(); // ['new-room', 'vip-lounge', ...]
240
- ```
241
-
242
- ## Contoh Penggunaan
243
-
244
- ### Chat Application
245
-
246
- ```typescript
247
- app.ws('/ws/chat', {
248
- auth: async (ctx) => {
249
- const token = ctx.query.token as string;
250
- return { id: generateId(), name: token || 'Anonymous' };
251
- },
252
-
253
- onConnect: async (socket, ctx) => {
254
- const ws = app.getWebSocket()!;
255
-
256
- // Join default room
257
- ws.joinRoom('general', socket);
258
-
259
- // Welcome message
260
- socket.send(JSON.stringify({
261
- type: 'welcome',
262
- message: `Welcome ${ctx.user.name}!`,
263
- room: 'general'
264
- }));
265
-
266
- // Notify others
267
- ws.broadcast('general', {
268
- type: 'system',
269
- message: `${ctx.user.name} joined the chat`
270
- });
271
- },
272
-
273
- onMessage: async (socket, message, ctx) => {
274
- const ws = app.getWebSocket()!;
275
-
276
- switch (message.type) {
277
- case 'chat':
278
- ws.broadcast(message.room || 'general', {
279
- type: 'chat',
280
- user: ctx.user.name,
281
- text: message.text,
282
- timestamp: new Date().toISOString()
283
- });
284
- break;
285
-
286
- case 'private':
287
- // Private message implementation
288
- // Find target socket and send directly
289
- break;
290
-
291
- case 'typing':
292
- ws.broadcast(message.room || 'general', {
293
- type: 'typing',
294
- user: ctx.user.name
295
- });
296
- break;
297
- }
298
- },
299
-
300
- onClose: async (socket, ctx) => {
301
- const ws = app.getWebSocket()!;
302
- ws.broadcast('general', {
303
- type: 'system',
304
- message: `${ctx.user.name} left the chat`
305
- });
306
- },
307
-
308
- onError: async (socket, error, ctx) => {
309
- console.error(`WebSocket error for ${ctx.user?.name}:`, error);
310
- }
311
- });
312
- ```
313
-
314
- ### Notifications Service
315
-
316
- ```typescript
317
- app.ws('/ws/notifications', {
318
- auth: async (ctx) => {
319
- const token = ctx.query.token as string;
320
- return await verifyToken(token);
321
- },
322
-
323
- onConnect: async (socket, ctx) => {
324
- const ws = app.getWebSocket()!;
325
-
326
- // Join user-specific room
327
- ws.joinRoom(`user:${ctx.user.id}`, socket);
328
-
329
- // Join role-based rooms
330
- ctx.user.roles.forEach((role: string) => {
331
- ws.joinRoom(`role:${role}`, socket);
332
- });
333
-
334
- // Send unread notifications
335
- const unread = await getUnreadNotifications(ctx.user.id);
336
- socket.send(JSON.stringify({
337
- type: 'unread',
338
- count: unread.length,
339
- notifications: unread
340
- }));
341
- },
342
-
343
- onMessage: async (socket, message, ctx) => {
344
- if (message.type === 'mark_read') {
345
- await markNotificationRead(message.notificationId, ctx.user.id);
346
- socket.send(JSON.stringify({
347
- type: 'marked_read',
348
- notificationId: message.notificationId
349
- }));
350
- }
351
- }
352
- });
353
-
354
- // Send notification from anywhere in your app
355
- function sendNotification(userId: string, notification: any) {
356
- const ws = app.getWebSocket();
357
- ws?.broadcast(`user:${userId}`, {
358
- type: 'notification',
359
- ...notification
360
- });
361
- }
362
-
363
- // Send to all admins
364
- function notifyAdmins(message: string) {
365
- const ws = app.getWebSocket();
366
- ws?.broadcast('role:admin', {
367
- type: 'admin_alert',
368
- message
369
- });
370
- }
371
- ```
372
-
373
- ### Live Updates / Real-time Data
374
-
375
- ```typescript
376
- app.ws('/ws/stocks', {
377
- onConnect: async (socket, ctx) => {
378
- const ws = app.getWebSocket()!;
379
- const symbols = (ctx.query.symbols as string)?.split(',') || ['AAPL', 'GOOGL'];
380
-
381
- // Subscribe to stock symbols
382
- symbols.forEach(symbol => {
383
- ws.joinRoom(`stock:${symbol}`, socket);
384
- });
385
-
386
- socket.send(JSON.stringify({
387
- type: 'subscribed',
388
- symbols
389
- }));
390
- },
391
-
392
- onMessage: async (socket, message, ctx) => {
393
- const ws = app.getWebSocket()!;
394
-
395
- if (message.type === 'subscribe') {
396
- ws.joinRoom(`stock:${message.symbol}`, socket);
397
- }
398
-
399
- if (message.type === 'unsubscribe') {
400
- ws.leaveRoom(`stock:${message.symbol}`, socket);
401
- }
402
- }
403
- });
404
-
405
- // Price update service (simulated)
406
- setInterval(() => {
407
- const ws = app.getWebSocket();
408
- const stocks = ['AAPL', 'GOOGL', 'MSFT', 'AMZN'];
409
-
410
- stocks.forEach(symbol => {
411
- ws?.broadcast(`stock:${symbol}`, {
412
- type: 'price_update',
413
- symbol,
414
- price: (Math.random() * 1000).toFixed(2),
415
- timestamp: new Date().toISOString()
416
- });
417
- });
418
- }, 1000);
419
- ```
420
-
421
- ### Multiplayer Game
422
-
423
- ```typescript
424
- app.ws('/ws/game', {
425
- auth: async (ctx) => {
426
- return {
427
- id: generatePlayerId(),
428
- name: ctx.query.name as string || 'Player'
429
- };
430
- },
431
-
432
- onConnect: async (socket, ctx) => {
433
- const ws = app.getWebSocket()!;
434
- const gameId = ctx.query.game as string;
435
-
436
- if (!gameId) {
437
- socket.send(JSON.stringify({ type: 'error', message: 'Game ID required' }));
438
- socket.close();
439
- return;
440
- }
441
-
442
- // Join game room
443
- ws.joinRoom(`game:${gameId}`, socket);
444
-
445
- // Notify other players
446
- ws.broadcast(`game:${gameId}`, {
447
- type: 'player_joined',
448
- player: ctx.user
449
- });
450
-
451
- // Send current game state
452
- const gameState = await getGameState(gameId);
453
- socket.send(JSON.stringify({
454
- type: 'game_state',
455
- state: gameState
456
- }));
457
- },
458
-
459
- onMessage: async (socket, message, ctx) => {
460
- const ws = app.getWebSocket()!;
461
- const gameId = ctx.query.game as string;
462
-
463
- switch (message.type) {
464
- case 'move':
465
- const result = await processMove(gameId, ctx.user.id, message.move);
466
- ws.broadcast(`game:${gameId}`, {
467
- type: 'move_made',
468
- player: ctx.user.id,
469
- move: message.move,
470
- result
471
- });
472
- break;
473
-
474
- case 'chat':
475
- ws.broadcast(`game:${gameId}`, {
476
- type: 'game_chat',
477
- player: ctx.user.name,
478
- text: message.text
479
- });
480
- break;
481
- }
482
- },
483
-
484
- onClose: async (socket, ctx) => {
485
- const ws = app.getWebSocket()!;
486
- const gameId = ctx.query.game as string;
487
-
488
- ws.broadcast(`game:${gameId}`, {
489
- type: 'player_left',
490
- player: ctx.user
491
- });
492
- }
493
- });
494
- ```
495
-
496
- ## Client-Side Examples
497
-
498
- ### Browser (Vanilla JavaScript)
499
-
500
- ```javascript
501
- // Basic connection
502
- const ws = new WebSocket('ws://localhost:3000/ws/chat?token=myname');
503
-
504
- ws.onopen = () => {
505
- console.log('Connected!');
506
- ws.send(JSON.stringify({ type: 'chat', text: 'Hello!' }));
507
- };
508
-
509
- ws.onmessage = (event) => {
510
- const data = JSON.parse(event.data);
511
- console.log('Received:', data);
512
- };
513
-
514
- ws.onclose = () => {
515
- console.log('Disconnected');
516
- };
517
-
518
- ws.onerror = (error) => {
519
- console.error('WebSocket error:', error);
520
- };
521
- ```
522
-
523
- ### React Hook
524
-
525
- ```typescript
526
- import { useEffect, useState, useCallback, useRef } from 'react';
527
-
528
- function useWebSocket(url: string) {
529
- const [messages, setMessages] = useState<any[]>([]);
530
- const [isConnected, setIsConnected] = useState(false);
531
- const wsRef = useRef<WebSocket | null>(null);
532
-
533
- useEffect(() => {
534
- const ws = new WebSocket(url);
535
- wsRef.current = ws;
536
-
537
- ws.onopen = () => setIsConnected(true);
538
- ws.onclose = () => setIsConnected(false);
539
- ws.onmessage = (event) => {
540
- const data = JSON.parse(event.data);
541
- setMessages(prev => [...prev, data]);
542
- };
543
-
544
- return () => ws.close();
545
- }, [url]);
546
-
547
- const send = useCallback((data: any) => {
548
- wsRef.current?.send(JSON.stringify(data));
549
- }, []);
550
-
551
- return { messages, isConnected, send };
552
- }
553
-
554
- // Usage
555
- function ChatComponent() {
556
- const { messages, isConnected, send } = useWebSocket('ws://localhost:3000/ws/chat?token=user1');
557
-
558
- return (
559
- <div>
560
- <p>Status: {isConnected ? 'Connected' : 'Disconnected'}</p>
561
- <button onClick={() => send({ type: 'chat', text: 'Hello!' })}>
562
- Send
563
- </button>
564
- <ul>
565
- {messages.map((msg, i) => (
566
- <li key={i}>{JSON.stringify(msg)}</li>
567
- ))}
568
- </ul>
569
- </div>
570
- );
571
- }
572
- ```
573
-
574
- ### Node.js Client
575
-
576
- ```typescript
577
- import WebSocket from 'ws';
578
-
579
- const ws = new WebSocket('ws://localhost:3000/ws/chat?token=bot');
580
-
581
- ws.on('open', () => {
582
- console.log('Connected to server');
583
- ws.send(JSON.stringify({ type: 'chat', text: 'Bot is online!' }));
584
- });
585
-
586
- ws.on('message', (data) => {
587
- const message = JSON.parse(data.toString());
588
- console.log('Received:', message);
589
-
590
- // Auto-reply to messages
591
- if (message.type === 'chat' && message.user !== 'bot') {
592
- ws.send(JSON.stringify({
593
- type: 'chat',
594
- text: `You said: ${message.text}`
595
- }));
596
- }
597
- });
598
-
599
- ws.on('close', () => {
600
- console.log('Disconnected');
601
- });
602
- ```
603
-
604
- ## Error Handling
605
-
606
- ```typescript
607
- app.ws('/ws/robust', {
608
- auth: async (ctx) => {
609
- try {
610
- const token = ctx.query.token as string;
611
- if (!token) {
612
- throw new Error('AUTH_REQUIRED');
613
- }
614
-
615
- const user = await verifyToken(token);
616
- if (!user) {
617
- throw new Error('INVALID_TOKEN');
618
- }
619
-
620
- return user;
621
- } catch (error) {
622
- // Throwing here will reject the connection
623
- throw error;
624
- }
625
- },
626
-
627
- onConnect: async (socket, ctx) => {
628
- try {
629
- // Setup logic
630
- const ws = app.getWebSocket()!;
631
- ws.joinRoom('main', socket);
632
- } catch (error) {
633
- socket.send(JSON.stringify({
634
- type: 'error',
635
- message: 'Failed to setup connection'
636
- }));
637
- socket.close(1011, 'Setup failed');
638
- }
639
- },
640
-
641
- onMessage: async (socket, message, ctx) => {
642
- try {
643
- // Validate message
644
- if (!message.type) {
645
- throw new Error('Message type required');
646
- }
647
-
648
- // Process message
649
- await processMessage(message, ctx);
650
-
651
- } catch (error) {
652
- socket.send(JSON.stringify({
653
- type: 'error',
654
- message: error.message
655
- }));
656
- }
657
- },
658
-
659
- onError: async (socket, error, ctx) => {
660
- console.error('WebSocket Error:', {
661
- user: ctx.user?.id,
662
- error: error.message,
663
- stack: error.stack
664
- });
665
-
666
- // Optionally send error to client
667
- if (socket.readyState === WebSocket.OPEN) {
668
- socket.send(JSON.stringify({
669
- type: 'error',
670
- message: 'Internal server error'
671
- }));
672
- }
673
- }
674
- });
675
- ```
676
-
677
- ## Best Practices
678
-
679
- ### 1. Heartbeat/Ping-Pong
680
-
681
- ```typescript
682
- app.ws('/ws/with-heartbeat', {
683
- onConnect: async (socket, ctx) => {
684
- // Send ping every 30 seconds
685
- const pingInterval = setInterval(() => {
686
- if (socket.readyState === WebSocket.OPEN) {
687
- socket.ping();
688
- }
689
- }, 30000);
690
-
691
- // Store interval in context for cleanup
692
- ctx.metadata = { pingInterval };
693
- },
694
-
695
- onClose: async (socket, ctx) => {
696
- // Clear ping interval
697
- clearInterval(ctx.metadata?.pingInterval);
698
- }
699
- });
700
- ```
701
-
702
- ### 2. Rate Limiting
703
-
704
- ```typescript
705
- const messageCount = new Map<string, number>();
706
-
707
- app.ws('/ws/rate-limited', {
708
- auth: async (ctx) => {
709
- return { id: ctx.query.userId as string };
710
- },
711
-
712
- onMessage: async (socket, message, ctx) => {
713
- const userId = ctx.user.id;
714
- const count = (messageCount.get(userId) || 0) + 1;
715
- messageCount.set(userId, count);
716
-
717
- // Reset count every minute
718
- setTimeout(() => {
719
- messageCount.set(userId, Math.max(0, (messageCount.get(userId) || 0) - 1));
720
- }, 60000);
721
-
722
- // Limit: 100 messages per minute
723
- if (count > 100) {
724
- socket.send(JSON.stringify({
725
- type: 'error',
726
- message: 'Rate limit exceeded'
727
- }));
728
- return;
729
- }
730
-
731
- // Process message...
732
- }
733
- });
734
- ```
735
-
736
- ### 3. Message Validation
737
-
738
- ```typescript
739
- import { z } from 'zod';
740
-
741
- const ChatMessageSchema = z.object({
742
- type: z.literal('chat'),
743
- room: z.string().optional(),
744
- text: z.string().min(1).max(1000)
745
- });
746
-
747
- const JoinRoomSchema = z.object({
748
- type: z.literal('join_room'),
749
- room: z.string().min(1).max(50)
750
- });
751
-
752
- const MessageSchema = z.discriminatedUnion('type', [
753
- ChatMessageSchema,
754
- JoinRoomSchema
755
- ]);
756
-
757
- app.ws('/ws/validated', {
758
- onMessage: async (socket, message, ctx) => {
759
- const result = MessageSchema.safeParse(message);
760
-
761
- if (!result.success) {
762
- socket.send(JSON.stringify({
763
- type: 'error',
764
- message: 'Invalid message format',
765
- errors: result.error.errors
766
- }));
767
- return;
768
- }
769
-
770
- const validatedMessage = result.data;
771
-
772
- // Now TypeScript knows the exact type
773
- switch (validatedMessage.type) {
774
- case 'chat':
775
- // validatedMessage.text is guaranteed to exist
776
- break;
777
- case 'join_room':
778
- // validatedMessage.room is guaranteed to exist
779
- break;
780
- }
781
- }
782
- });
783
- ```
784
-
785
- ## WebSocket vs HTTP
786
-
787
- | Feature | WebSocket | HTTP |
788
- |---------|-----------|------|
789
- | Connection | Persistent | Request/Response |
790
- | Direction | Bidirectional | Client → Server |
791
- | Overhead | Low (after handshake) | High (headers per request) |
792
- | Use Case | Real-time, streaming | CRUD operations |
793
- | Scaling | More complex | Easier |
794
-
795
- **Gunakan WebSocket untuk:**
796
- - Chat applications
797
- - Live notifications
798
- - Real-time dashboards
799
- - Multiplayer games
800
- - Collaborative editing
801
- - Live streaming data
802
-
803
- **Gunakan HTTP untuk:**
804
- - CRUD operations
805
- - File uploads
806
- - Authentication endpoints
807
- - One-time requests
808
-
809
- ---
810
-
811
- **Related:**
812
- - [03-routing.md](./03-routing.md) - HTTP Routing
813
- - [19-class-based-routing.md](./19-class-based-routing.md) - Class-based Routes