@kood/claude-code 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +297 -0
- package/package.json +47 -0
- package/templates/hono/CLAUDE.md +376 -0
- package/templates/hono/docs/deployment/cloudflare.md +328 -0
- package/templates/hono/docs/deployment/index.md +291 -0
- package/templates/hono/docs/git/index.md +180 -0
- package/templates/hono/docs/library/hono/error-handling.md +400 -0
- package/templates/hono/docs/library/hono/index.md +241 -0
- package/templates/hono/docs/library/hono/middleware.md +334 -0
- package/templates/hono/docs/library/hono/rpc.md +454 -0
- package/templates/hono/docs/library/hono/validation.md +328 -0
- package/templates/hono/docs/library/prisma/index.md +427 -0
- package/templates/hono/docs/library/zod/index.md +413 -0
- package/templates/hono/docs/mcp/context7.md +106 -0
- package/templates/hono/docs/mcp/index.md +94 -0
- package/templates/hono/docs/mcp/sequential-thinking.md +101 -0
- package/templates/hono/docs/mcp/sgrep.md +105 -0
- package/templates/hono/docs/skills/gemini-review/SKILL.md +220 -0
- package/templates/hono/docs/skills/gemini-review/references/checklists.md +136 -0
- package/templates/hono/docs/skills/gemini-review/references/prompt-templates.md +303 -0
- package/templates/tanstack-start/CLAUDE.md +279 -0
- package/templates/tanstack-start/docs/architecture/architecture.md +547 -0
- package/templates/tanstack-start/docs/deployment/cloudflare.md +346 -0
- package/templates/tanstack-start/docs/deployment/index.md +102 -0
- package/templates/tanstack-start/docs/deployment/nitro.md +211 -0
- package/templates/tanstack-start/docs/deployment/railway.md +364 -0
- package/templates/tanstack-start/docs/deployment/vercel.md +287 -0
- package/templates/tanstack-start/docs/design/accessibility.md +433 -0
- package/templates/tanstack-start/docs/design/color.md +235 -0
- package/templates/tanstack-start/docs/design/components.md +409 -0
- package/templates/tanstack-start/docs/design/index.md +107 -0
- package/templates/tanstack-start/docs/design/safe-area.md +317 -0
- package/templates/tanstack-start/docs/design/spacing.md +341 -0
- package/templates/tanstack-start/docs/design/tailwind-setup.md +470 -0
- package/templates/tanstack-start/docs/design/typography.md +324 -0
- package/templates/tanstack-start/docs/git/index.md +203 -0
- package/templates/tanstack-start/docs/guides/best-practices.md +753 -0
- package/templates/tanstack-start/docs/guides/getting-started.md +304 -0
- package/templates/tanstack-start/docs/guides/husky-lint-staged.md +303 -0
- package/templates/tanstack-start/docs/guides/prettier.md +189 -0
- package/templates/tanstack-start/docs/guides/project-templates.md +710 -0
- package/templates/tanstack-start/docs/library/better-auth/2fa.md +136 -0
- package/templates/tanstack-start/docs/library/better-auth/advanced.md +138 -0
- package/templates/tanstack-start/docs/library/better-auth/index.md +83 -0
- package/templates/tanstack-start/docs/library/better-auth/plugins.md +111 -0
- package/templates/tanstack-start/docs/library/better-auth/session.md +127 -0
- package/templates/tanstack-start/docs/library/better-auth/setup.md +123 -0
- package/templates/tanstack-start/docs/library/prisma/crud.md +218 -0
- package/templates/tanstack-start/docs/library/prisma/index.md +165 -0
- package/templates/tanstack-start/docs/library/prisma/relations.md +191 -0
- package/templates/tanstack-start/docs/library/prisma/schema.md +177 -0
- package/templates/tanstack-start/docs/library/prisma/setup.md +156 -0
- package/templates/tanstack-start/docs/library/prisma/transactions.md +140 -0
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +146 -0
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +146 -0
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +196 -0
- package/templates/tanstack-start/docs/library/tanstack-query/setup.md +110 -0
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +170 -0
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +173 -0
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +171 -0
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +114 -0
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +142 -0
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +163 -0
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +128 -0
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +85 -0
- package/templates/tanstack-start/docs/library/zod/basic-types.md +186 -0
- package/templates/tanstack-start/docs/library/zod/complex-types.md +204 -0
- package/templates/tanstack-start/docs/library/zod/index.md +186 -0
- package/templates/tanstack-start/docs/library/zod/transforms.md +174 -0
- package/templates/tanstack-start/docs/library/zod/validation.md +208 -0
- package/templates/tanstack-start/docs/mcp/context7.md +204 -0
- package/templates/tanstack-start/docs/mcp/index.md +116 -0
- package/templates/tanstack-start/docs/mcp/sequential-thinking.md +180 -0
- package/templates/tanstack-start/docs/mcp/sgrep.md +174 -0
- package/templates/tanstack-start/docs/skills/gemini-review/SKILL.md +220 -0
- package/templates/tanstack-start/docs/skills/gemini-review/references/checklists.md +150 -0
- package/templates/tanstack-start/docs/skills/gemini-review/references/prompt-templates.md +293 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
# Hono 에러 처리
|
|
2
|
+
|
|
3
|
+
> HTTPException과 onError를 사용한 체계적인 에러 관리
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## HTTPException
|
|
8
|
+
|
|
9
|
+
### 기본 사용
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { Hono } from 'hono'
|
|
13
|
+
import { HTTPException } from 'hono/http-exception'
|
|
14
|
+
|
|
15
|
+
const app = new Hono()
|
|
16
|
+
|
|
17
|
+
app.get('/users/:id', async (c) => {
|
|
18
|
+
const id = c.req.param('id')
|
|
19
|
+
const user = await prisma.user.findUnique({ where: { id } })
|
|
20
|
+
|
|
21
|
+
if (!user) {
|
|
22
|
+
throw new HTTPException(404, { message: 'User not found' })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return c.json({ user })
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### HTTPException 옵션
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
throw new HTTPException(400, {
|
|
33
|
+
message: 'Invalid request', // 에러 메시지
|
|
34
|
+
cause: originalError, // 원인 에러
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 일반적인 HTTP 상태 코드
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// 400 Bad Request - 잘못된 요청
|
|
42
|
+
throw new HTTPException(400, { message: 'Invalid input' })
|
|
43
|
+
|
|
44
|
+
// 401 Unauthorized - 인증 필요
|
|
45
|
+
throw new HTTPException(401, { message: 'Authentication required' })
|
|
46
|
+
|
|
47
|
+
// 403 Forbidden - 권한 없음
|
|
48
|
+
throw new HTTPException(403, { message: 'Access denied' })
|
|
49
|
+
|
|
50
|
+
// 404 Not Found - 리소스 없음
|
|
51
|
+
throw new HTTPException(404, { message: 'Resource not found' })
|
|
52
|
+
|
|
53
|
+
// 409 Conflict - 충돌
|
|
54
|
+
throw new HTTPException(409, { message: 'Resource already exists' })
|
|
55
|
+
|
|
56
|
+
// 422 Unprocessable Entity - 검증 실패
|
|
57
|
+
throw new HTTPException(422, { message: 'Validation failed' })
|
|
58
|
+
|
|
59
|
+
// 429 Too Many Requests - 요청 제한 초과
|
|
60
|
+
throw new HTTPException(429, { message: 'Rate limit exceeded' })
|
|
61
|
+
|
|
62
|
+
// 500 Internal Server Error - 서버 에러
|
|
63
|
+
throw new HTTPException(500, { message: 'Internal server error' })
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 글로벌 에러 핸들러
|
|
69
|
+
|
|
70
|
+
### onError
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { Hono } from 'hono'
|
|
74
|
+
import { HTTPException } from 'hono/http-exception'
|
|
75
|
+
|
|
76
|
+
const app = new Hono()
|
|
77
|
+
|
|
78
|
+
app.onError((err, c) => {
|
|
79
|
+
console.error(`${err}`)
|
|
80
|
+
|
|
81
|
+
// HTTPException 처리
|
|
82
|
+
if (err instanceof HTTPException) {
|
|
83
|
+
return c.json(
|
|
84
|
+
{
|
|
85
|
+
error: err.message,
|
|
86
|
+
status: err.status,
|
|
87
|
+
},
|
|
88
|
+
err.status
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 기타 에러
|
|
93
|
+
return c.json(
|
|
94
|
+
{
|
|
95
|
+
error: 'Internal Server Error',
|
|
96
|
+
message: err.message,
|
|
97
|
+
},
|
|
98
|
+
500
|
|
99
|
+
)
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 상세 에러 응답
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
app.onError((err, c) => {
|
|
107
|
+
const requestId = c.get('requestId')
|
|
108
|
+
|
|
109
|
+
// HTTPException
|
|
110
|
+
if (err instanceof HTTPException) {
|
|
111
|
+
return c.json(
|
|
112
|
+
{
|
|
113
|
+
success: false,
|
|
114
|
+
error: {
|
|
115
|
+
code: err.status,
|
|
116
|
+
message: err.message,
|
|
117
|
+
requestId,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
err.status
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Zod 검증 에러 (이미 zValidator에서 처리된 경우)
|
|
125
|
+
if (err.name === 'ZodError') {
|
|
126
|
+
return c.json(
|
|
127
|
+
{
|
|
128
|
+
success: false,
|
|
129
|
+
error: {
|
|
130
|
+
code: 400,
|
|
131
|
+
message: 'Validation failed',
|
|
132
|
+
details: err.flatten(),
|
|
133
|
+
requestId,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
400
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 개발 환경에서만 스택 트레이스 포함
|
|
141
|
+
const isDev = c.env.NODE_ENV === 'development'
|
|
142
|
+
|
|
143
|
+
return c.json(
|
|
144
|
+
{
|
|
145
|
+
success: false,
|
|
146
|
+
error: {
|
|
147
|
+
code: 500,
|
|
148
|
+
message: 'Internal Server Error',
|
|
149
|
+
...(isDev && { stack: err.stack }),
|
|
150
|
+
requestId,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
500
|
|
154
|
+
)
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 404 핸들러
|
|
161
|
+
|
|
162
|
+
### notFound
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
app.notFound((c) => {
|
|
166
|
+
return c.json(
|
|
167
|
+
{
|
|
168
|
+
error: 'Not Found',
|
|
169
|
+
path: c.req.path,
|
|
170
|
+
method: c.req.method,
|
|
171
|
+
},
|
|
172
|
+
404
|
|
173
|
+
)
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 라우트별 에러 핸들러
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const api = new Hono()
|
|
183
|
+
|
|
184
|
+
// API 전용 에러 핸들러 (우선순위 높음)
|
|
185
|
+
api.onError((err, c) => {
|
|
186
|
+
console.log('API-specific error handler')
|
|
187
|
+
|
|
188
|
+
if (err instanceof HTTPException) {
|
|
189
|
+
return c.json({ apiError: err.message }, err.status)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return c.json({ apiError: err.message }, 500)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
api.get('/error', () => {
|
|
196
|
+
throw new Error('API error')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
app.route('/api', api)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 미들웨어에서 에러 접근
|
|
205
|
+
|
|
206
|
+
### c.error 사용
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
app.use(async (c, next) => {
|
|
210
|
+
await next()
|
|
211
|
+
|
|
212
|
+
// 핸들러에서 발생한 에러 접근
|
|
213
|
+
if (c.error) {
|
|
214
|
+
// 로깅, 모니터링 등
|
|
215
|
+
console.error('Error occurred:', c.error)
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 커스텀 에러 클래스
|
|
223
|
+
|
|
224
|
+
### errors.ts
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { HTTPException } from 'hono/http-exception'
|
|
228
|
+
|
|
229
|
+
export class NotFoundError extends HTTPException {
|
|
230
|
+
constructor(resource: string) {
|
|
231
|
+
super(404, { message: `${resource} not found` })
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export class UnauthorizedError extends HTTPException {
|
|
236
|
+
constructor(message = 'Unauthorized') {
|
|
237
|
+
super(401, { message })
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export class ForbiddenError extends HTTPException {
|
|
242
|
+
constructor(message = 'Access denied') {
|
|
243
|
+
super(403, { message })
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export class ValidationError extends HTTPException {
|
|
248
|
+
constructor(message: string, public details?: unknown) {
|
|
249
|
+
super(422, { message })
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export class ConflictError extends HTTPException {
|
|
254
|
+
constructor(resource: string) {
|
|
255
|
+
super(409, { message: `${resource} already exists` })
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### 사용
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { NotFoundError, ConflictError } from '@/lib/errors'
|
|
264
|
+
|
|
265
|
+
app.get('/users/:id', async (c) => {
|
|
266
|
+
const user = await prisma.user.findUnique({ where: { id } })
|
|
267
|
+
|
|
268
|
+
if (!user) {
|
|
269
|
+
throw new NotFoundError('User')
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return c.json({ user })
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
app.post('/users', async (c) => {
|
|
276
|
+
const data = c.req.valid('json')
|
|
277
|
+
const existing = await prisma.user.findUnique({
|
|
278
|
+
where: { email: data.email },
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
if (existing) {
|
|
282
|
+
throw new ConflictError('User with this email')
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const user = await prisma.user.create({ data })
|
|
286
|
+
return c.json({ user }, 201)
|
|
287
|
+
})
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 스트리밍 에러 처리
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
import { stream } from 'hono/streaming'
|
|
296
|
+
|
|
297
|
+
app.get('/stream', (c) => {
|
|
298
|
+
return stream(
|
|
299
|
+
c,
|
|
300
|
+
async (stream) => {
|
|
301
|
+
stream.onAbort(() => {
|
|
302
|
+
console.log('Stream aborted')
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
await stream.write(new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]))
|
|
306
|
+
},
|
|
307
|
+
// 에러 핸들러 (선택적)
|
|
308
|
+
(err, stream) => {
|
|
309
|
+
stream.writeln('An error occurred!')
|
|
310
|
+
console.error(err)
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
})
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 완전한 에러 처리 설정
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import { Hono } from 'hono'
|
|
322
|
+
import { HTTPException } from 'hono/http-exception'
|
|
323
|
+
import { logger } from 'hono/logger'
|
|
324
|
+
import { requestId } from 'hono/request-id'
|
|
325
|
+
|
|
326
|
+
type Env = {
|
|
327
|
+
Bindings: {
|
|
328
|
+
NODE_ENV: string
|
|
329
|
+
}
|
|
330
|
+
Variables: {
|
|
331
|
+
requestId: string
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const app = new Hono<Env>()
|
|
336
|
+
|
|
337
|
+
// 미들웨어
|
|
338
|
+
app.use(logger())
|
|
339
|
+
app.use(requestId())
|
|
340
|
+
|
|
341
|
+
// 글로벌 에러 핸들러
|
|
342
|
+
app.onError((err, c) => {
|
|
343
|
+
const reqId = c.get('requestId')
|
|
344
|
+
const isDev = c.env.NODE_ENV === 'development'
|
|
345
|
+
|
|
346
|
+
console.error(`[${reqId}] Error:`, err)
|
|
347
|
+
|
|
348
|
+
if (err instanceof HTTPException) {
|
|
349
|
+
return c.json(
|
|
350
|
+
{
|
|
351
|
+
success: false,
|
|
352
|
+
error: {
|
|
353
|
+
status: err.status,
|
|
354
|
+
message: err.message,
|
|
355
|
+
requestId: reqId,
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
err.status
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return c.json(
|
|
363
|
+
{
|
|
364
|
+
success: false,
|
|
365
|
+
error: {
|
|
366
|
+
status: 500,
|
|
367
|
+
message: 'Internal Server Error',
|
|
368
|
+
requestId: reqId,
|
|
369
|
+
...(isDev && { stack: err.stack }),
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
500
|
|
373
|
+
)
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
// 404 핸들러
|
|
377
|
+
app.notFound((c) => {
|
|
378
|
+
return c.json(
|
|
379
|
+
{
|
|
380
|
+
success: false,
|
|
381
|
+
error: {
|
|
382
|
+
status: 404,
|
|
383
|
+
message: 'Not Found',
|
|
384
|
+
path: c.req.path,
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
404
|
|
388
|
+
)
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
export default app
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## 관련 문서
|
|
397
|
+
|
|
398
|
+
- [기본 사용법](./index.md)
|
|
399
|
+
- [미들웨어](./middleware.md)
|
|
400
|
+
- [Zod 검증](./validation.md)
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# Hono - Web Framework
|
|
2
|
+
|
|
3
|
+
> Web Standards 기반 초경량, 초고속 서버 프레임워크
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 개요
|
|
8
|
+
|
|
9
|
+
Hono는 Cloudflare Workers, Deno, Bun, Node.js 등 모든 JavaScript 런타임에서 동작하는 서버 프레임워크입니다.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 설치
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# npm
|
|
17
|
+
npm install hono
|
|
18
|
+
|
|
19
|
+
# bun
|
|
20
|
+
bun add hono
|
|
21
|
+
|
|
22
|
+
# yarn
|
|
23
|
+
yarn add hono
|
|
24
|
+
|
|
25
|
+
# pnpm
|
|
26
|
+
pnpm add hono
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 기본 사용법
|
|
32
|
+
|
|
33
|
+
### App 생성
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { Hono } from 'hono'
|
|
37
|
+
|
|
38
|
+
const app = new Hono()
|
|
39
|
+
|
|
40
|
+
app.get('/', (c) => {
|
|
41
|
+
return c.text('Hello Hono!')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
export default app
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### HTTP 메서드
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const app = new Hono()
|
|
51
|
+
|
|
52
|
+
app.get('/', (c) => c.text('GET /'))
|
|
53
|
+
app.post('/', (c) => c.text('POST /'))
|
|
54
|
+
app.put('/', (c) => c.text('PUT /'))
|
|
55
|
+
app.delete('/', (c) => c.text('DELETE /'))
|
|
56
|
+
app.patch('/', (c) => c.text('PATCH /'))
|
|
57
|
+
|
|
58
|
+
// 모든 메서드
|
|
59
|
+
app.all('/hello', (c) => c.text('Any Method /hello'))
|
|
60
|
+
|
|
61
|
+
// 여러 메서드
|
|
62
|
+
app.on(['PUT', 'DELETE'], '/post', (c) => c.text('PUT or DELETE /post'))
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 라우팅
|
|
68
|
+
|
|
69
|
+
### 기본 라우팅
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// 정적 경로
|
|
73
|
+
app.get('/users', (c) => c.json({ users: [] }))
|
|
74
|
+
|
|
75
|
+
// 동적 파라미터
|
|
76
|
+
app.get('/users/:id', (c) => {
|
|
77
|
+
const id = c.req.param('id')
|
|
78
|
+
return c.json({ id })
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// 여러 파라미터
|
|
82
|
+
app.get('/posts/:postId/comments/:commentId', (c) => {
|
|
83
|
+
const { postId, commentId } = c.req.param()
|
|
84
|
+
return c.json({ postId, commentId })
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 와일드카드
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// 와일드카드 매칭
|
|
92
|
+
app.get('/wild/*/card', (c) => {
|
|
93
|
+
return c.text('GET /wild/*/card')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
// 모든 하위 경로
|
|
97
|
+
app.get('/api/*', (c) => {
|
|
98
|
+
return c.text('API catch-all')
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 라우트 그룹화
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const app = new Hono()
|
|
106
|
+
|
|
107
|
+
// 서브 라우트
|
|
108
|
+
const users = new Hono()
|
|
109
|
+
users.get('/', (c) => c.json({ users: [] }))
|
|
110
|
+
users.get('/:id', (c) => c.json({ id: c.req.param('id') }))
|
|
111
|
+
users.post('/', (c) => c.json({ created: true }, 201))
|
|
112
|
+
|
|
113
|
+
// 마운트
|
|
114
|
+
app.route('/users', users)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Context (c)
|
|
120
|
+
|
|
121
|
+
### Request 정보
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
app.get('/info', (c) => {
|
|
125
|
+
// URL 정보
|
|
126
|
+
const url = c.req.url
|
|
127
|
+
const path = c.req.path
|
|
128
|
+
const method = c.req.method
|
|
129
|
+
|
|
130
|
+
// 파라미터
|
|
131
|
+
const id = c.req.param('id')
|
|
132
|
+
const params = c.req.param() // 모든 파라미터
|
|
133
|
+
|
|
134
|
+
// 쿼리 스트링
|
|
135
|
+
const page = c.req.query('page')
|
|
136
|
+
const queries = c.req.query() // 모든 쿼리
|
|
137
|
+
|
|
138
|
+
// 헤더
|
|
139
|
+
const auth = c.req.header('Authorization')
|
|
140
|
+
|
|
141
|
+
return c.json({ url, path, method })
|
|
142
|
+
})
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Request Body
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// JSON
|
|
149
|
+
app.post('/json', async (c) => {
|
|
150
|
+
const body = await c.req.json()
|
|
151
|
+
return c.json(body)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Form Data
|
|
155
|
+
app.post('/form', async (c) => {
|
|
156
|
+
const body = await c.req.parseBody()
|
|
157
|
+
return c.json(body)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// Raw Text
|
|
161
|
+
app.post('/text', async (c) => {
|
|
162
|
+
const text = await c.req.text()
|
|
163
|
+
return c.text(text)
|
|
164
|
+
})
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Response 메서드
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// Text
|
|
171
|
+
app.get('/text', (c) => c.text('Hello'))
|
|
172
|
+
|
|
173
|
+
// JSON
|
|
174
|
+
app.get('/json', (c) => c.json({ message: 'Hello' }))
|
|
175
|
+
|
|
176
|
+
// JSON with status
|
|
177
|
+
app.post('/created', (c) => c.json({ id: 1 }, 201))
|
|
178
|
+
|
|
179
|
+
// HTML
|
|
180
|
+
app.get('/html', (c) => c.html('<h1>Hello</h1>'))
|
|
181
|
+
|
|
182
|
+
// Redirect
|
|
183
|
+
app.get('/old', (c) => c.redirect('/new'))
|
|
184
|
+
|
|
185
|
+
// Not Found
|
|
186
|
+
app.get('/404', (c) => c.notFound())
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 환경 변수 (Bindings)
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
type Bindings = {
|
|
195
|
+
DATABASE_URL: string
|
|
196
|
+
JWT_SECRET: string
|
|
197
|
+
MY_BUCKET: R2Bucket // Cloudflare R2
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const app = new Hono<{ Bindings: Bindings }>()
|
|
201
|
+
|
|
202
|
+
app.get('/', (c) => {
|
|
203
|
+
const dbUrl = c.env.DATABASE_URL
|
|
204
|
+
const secret = c.env.JWT_SECRET
|
|
205
|
+
return c.json({ connected: true })
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Variables (상태 공유)
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
type Variables = {
|
|
215
|
+
userId: string
|
|
216
|
+
user: { id: string; name: string }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const app = new Hono<{ Variables: Variables }>()
|
|
220
|
+
|
|
221
|
+
// 미들웨어에서 설정
|
|
222
|
+
app.use(async (c, next) => {
|
|
223
|
+
c.set('userId', '123')
|
|
224
|
+
await next()
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
// 핸들러에서 사용
|
|
228
|
+
app.get('/me', (c) => {
|
|
229
|
+
const userId = c.get('userId')
|
|
230
|
+
return c.json({ userId })
|
|
231
|
+
})
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 관련 문서
|
|
237
|
+
|
|
238
|
+
- [미들웨어](./middleware.md)
|
|
239
|
+
- [Zod 검증](./validation.md)
|
|
240
|
+
- [에러 처리](./error-handling.md)
|
|
241
|
+
- [RPC Client](./rpc.md)
|