@kood/claude-code 0.1.6 → 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/index.js +109 -216
- package/package.json +8 -2
- package/templates/hono/CLAUDE.md +59 -328
- package/templates/hono/docs/architecture/architecture.md +93 -747
- package/templates/hono/docs/deployment/cloudflare.md +59 -513
- package/templates/hono/docs/deployment/docker.md +41 -356
- package/templates/hono/docs/deployment/index.md +54 -190
- package/templates/hono/docs/deployment/railway.md +36 -306
- package/templates/hono/docs/deployment/vercel.md +49 -434
- package/templates/hono/docs/library/ai-sdk/index.md +53 -290
- package/templates/hono/docs/library/ai-sdk/openrouter.md +19 -387
- package/templates/hono/docs/library/ai-sdk/providers.md +28 -394
- package/templates/hono/docs/library/ai-sdk/streaming.md +52 -353
- package/templates/hono/docs/library/ai-sdk/structured-output.md +63 -395
- package/templates/hono/docs/library/ai-sdk/tools.md +62 -431
- package/templates/hono/docs/library/hono/env-setup.md +24 -313
- package/templates/hono/docs/library/hono/error-handling.md +34 -295
- package/templates/hono/docs/library/hono/index.md +29 -121
- package/templates/hono/docs/library/hono/middleware.md +21 -188
- package/templates/hono/docs/library/hono/rpc.md +40 -341
- package/templates/hono/docs/library/hono/validation.md +35 -195
- package/templates/hono/docs/library/pino/index.md +42 -333
- package/templates/hono/docs/library/prisma/cloudflare-d1.md +64 -367
- package/templates/hono/docs/library/prisma/config.md +19 -260
- package/templates/hono/docs/library/prisma/index.md +67 -320
- package/templates/hono/docs/library/zod/index.md +53 -257
- package/templates/npx/CLAUDE.md +62 -274
- package/templates/npx/docs/references/patterns.md +160 -0
- package/templates/tanstack-start/CLAUDE.md +100 -256
- package/templates/tanstack-start/docs/architecture/architecture.md +44 -589
- package/templates/tanstack-start/docs/deployment/cloudflare.md +37 -424
- package/templates/tanstack-start/docs/deployment/index.md +57 -286
- package/templates/tanstack-start/docs/deployment/nitro.md +36 -318
- package/templates/tanstack-start/docs/deployment/railway.md +40 -409
- package/templates/tanstack-start/docs/deployment/vercel.md +43 -465
- package/templates/tanstack-start/docs/design/components.md +77 -311
- package/templates/tanstack-start/docs/design/index.md +113 -69
- package/templates/tanstack-start/docs/design/safe-area.md +51 -250
- package/templates/tanstack-start/docs/design/tailwind-setup.md +45 -359
- package/templates/tanstack-start/docs/guides/conventions.md +103 -0
- package/templates/tanstack-start/docs/guides/env-setup.md +34 -340
- package/templates/tanstack-start/docs/guides/getting-started.md +22 -209
- package/templates/tanstack-start/docs/guides/hooks.md +166 -0
- package/templates/tanstack-start/docs/guides/routes.md +166 -0
- package/templates/tanstack-start/docs/guides/services.md +143 -0
- package/templates/tanstack-start/docs/library/better-auth/2fa.md +27 -115
- package/templates/tanstack-start/docs/library/better-auth/advanced.md +22 -105
- package/templates/tanstack-start/docs/library/better-auth/index.md +17 -66
- package/templates/tanstack-start/docs/library/better-auth/plugins.md +11 -88
- package/templates/tanstack-start/docs/library/better-auth/session.md +12 -92
- package/templates/tanstack-start/docs/library/better-auth/setup.md +9 -91
- package/templates/tanstack-start/docs/library/prisma/cloudflare-d1.md +30 -358
- package/templates/tanstack-start/docs/library/prisma/config.md +27 -327
- package/templates/tanstack-start/docs/library/prisma/crud.md +46 -174
- package/templates/tanstack-start/docs/library/prisma/index.md +23 -113
- package/templates/tanstack-start/docs/library/prisma/relations.md +31 -153
- package/templates/tanstack-start/docs/library/prisma/schema.md +40 -217
- package/templates/tanstack-start/docs/library/prisma/setup.md +12 -112
- package/templates/tanstack-start/docs/library/prisma/transactions.md +20 -110
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +26 -97
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +28 -107
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +44 -146
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +33 -127
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +49 -149
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +19 -112
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +33 -80
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +28 -106
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +21 -118
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +34 -246
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +6 -39
- package/templates/tanstack-start/docs/library/zod/complex-types.md +32 -156
- package/templates/tanstack-start/docs/library/zod/index.md +31 -144
- package/templates/tanstack-start/docs/library/zod/transforms.md +20 -129
- package/templates/tanstack-start/docs/library/zod/validation.md +39 -155
- package/templates/hono/docs/commands/git.md +0 -145
- package/templates/hono/docs/mcp/context7.md +0 -106
- package/templates/hono/docs/mcp/index.md +0 -176
- package/templates/hono/docs/mcp/sequential-thinking.md +0 -101
- package/templates/hono/docs/mcp/serena.md +0 -269
- package/templates/hono/docs/mcp/sgrep.md +0 -105
- package/templates/hono/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/hono/docs/skills/gemini-review/references/checklists.md +0 -136
- package/templates/hono/docs/skills/gemini-review/references/prompt-templates.md +0 -303
- package/templates/npx/docs/commands/git.md +0 -145
- package/templates/npx/docs/mcp/index.md +0 -60
- package/templates/npx/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/npx/docs/skills/gemini-review/references/checklists.md +0 -134
- package/templates/npx/docs/skills/gemini-review/references/prompt-templates.md +0 -301
- package/templates/tanstack-start/docs/commands/git.md +0 -145
- package/templates/tanstack-start/docs/design/accessibility.md +0 -433
- package/templates/tanstack-start/docs/design/color.md +0 -235
- package/templates/tanstack-start/docs/design/spacing.md +0 -341
- package/templates/tanstack-start/docs/design/typography.md +0 -324
- package/templates/tanstack-start/docs/guides/best-practices.md +0 -950
- package/templates/tanstack-start/docs/guides/husky-lint-staged.md +0 -303
- package/templates/tanstack-start/docs/guides/prettier.md +0 -189
- package/templates/tanstack-start/docs/guides/project-templates.md +0 -710
- package/templates/tanstack-start/docs/library/tanstack-query/setup.md +0 -107
- package/templates/tanstack-start/docs/library/zod/basic-types.md +0 -186
- package/templates/tanstack-start/docs/mcp/context7.md +0 -204
- package/templates/tanstack-start/docs/mcp/index.md +0 -177
- package/templates/tanstack-start/docs/mcp/sequential-thinking.md +0 -180
- package/templates/tanstack-start/docs/mcp/serena.md +0 -269
- package/templates/tanstack-start/docs/mcp/sgrep.md +0 -174
- package/templates/tanstack-start/docs/skills/gemini-review/SKILL.md +0 -220
- package/templates/tanstack-start/docs/skills/gemini-review/references/checklists.md +0 -144
- package/templates/tanstack-start/docs/skills/gemini-review/references/prompt-templates.md +0 -292
|
@@ -1,909 +1,255 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Hono 서버 아키텍처
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> 레이어 기반 아키텍처
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 시스템 개요
|
|
6
8
|
|
|
7
9
|
```
|
|
8
|
-
|
|
9
|
-
│ Client (Browser/App) │
|
|
10
|
-
│ │
|
|
11
|
-
│ ┌────────────────┐ ┌────────────────┐ ┌───────────────┐ │
|
|
12
|
-
│ │ HTTP Client │───▶│ hono/client │───▶│ React UI │ │
|
|
13
|
-
│ │ (fetch/axios) │◀───│ (RPC Client) │◀───│ (Components) │ │
|
|
14
|
-
│ └────────────────┘ └───────┬────────┘ └───────────────┘ │
|
|
15
|
-
│ │ │
|
|
16
|
-
└────────────────────────────────┼─────────────────────────────────┘
|
|
17
|
-
│
|
|
18
|
-
▼
|
|
19
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
20
|
-
│ Hono Server │
|
|
21
|
-
│ │
|
|
22
|
-
│ ┌────────────────────────────────────────────────────────────┐ │
|
|
23
|
-
│ │ Middleware Stack │ │
|
|
24
|
-
│ │ - logger → 요청 로깅 │ │
|
|
25
|
-
│ │ - cors → CORS 설정 │ │
|
|
26
|
-
│ │ - secureHeaders → 보안 헤더 │ │
|
|
27
|
-
│ │ - authMiddleware → 인증 처리 │ │
|
|
28
|
-
│ └────────────────────────┬───────────────────────────────────┘ │
|
|
29
|
-
│ │ │
|
|
30
|
-
│ ┌────────────────────────▼───────────────────────────────────┐ │
|
|
31
|
-
│ │ Routes Layer │ │
|
|
32
|
-
│ │ - routes/users.ts → /users/* │ │
|
|
33
|
-
│ │ - routes/posts.ts → /posts/* │ │
|
|
34
|
-
│ │ - routes/auth.ts → /auth/* │ │
|
|
35
|
-
│ └────────────────────────┬───────────────────────────────────┘ │
|
|
36
|
-
│ │ │
|
|
37
|
-
│ ┌────────────────────────▼───────────────────────────────────┐ │
|
|
38
|
-
│ │ Validation Layer │ │
|
|
39
|
-
│ │ - @hono/zod-validator → 요청 검증 │ │
|
|
40
|
-
│ │ - Zod v4 Schemas → 타입 안전 검증 │ │
|
|
41
|
-
│ └────────────────────────┬───────────────────────────────────┘ │
|
|
42
|
-
│ │ │
|
|
43
|
-
│ ┌────────────────────────▼───────────────────────────────────┐ │
|
|
44
|
-
│ │ Services Layer │ │
|
|
45
|
-
│ │ - Business Logic │ │
|
|
46
|
-
│ │ - Data Transformation │ │
|
|
47
|
-
│ │ - Error Handling (HTTPException) │ │
|
|
48
|
-
│ └────────────────────────┬───────────────────────────────────┘ │
|
|
49
|
-
│ │ │
|
|
50
|
-
└───────────────────────────┼──────────────────────────────────────┘
|
|
51
|
-
│
|
|
52
|
-
▼
|
|
53
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
54
|
-
│ Database Layer │
|
|
55
|
-
│ │
|
|
56
|
-
│ ┌────────────────┐ ┌────────────────┐ ┌───────────────┐ │
|
|
57
|
-
│ │ Prisma Client │───▶│ PostgreSQL │ │ Cloudflare │ │
|
|
58
|
-
│ │ (ORM v7) │ │ (Primary) │ │ D1/KV/R2 │ │
|
|
59
|
-
│ └────────────────┘ └────────────────┘ └───────────────┘ │
|
|
60
|
-
│ │
|
|
61
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
10
|
+
Client → Middleware → Routes → Validation → Services → Database
|
|
62
11
|
```
|
|
63
12
|
|
|
64
|
-
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 프로젝트 구조
|
|
65
16
|
|
|
66
17
|
```
|
|
67
|
-
|
|
68
|
-
├──
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
│
|
|
74
|
-
│
|
|
75
|
-
│
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
│ │ └── rate-limit.ts # 레이트 리밋 미들웨어
|
|
80
|
-
│ ├── validators/ # Zod 스키마 정의
|
|
81
|
-
│ │ ├── index.ts # re-export
|
|
82
|
-
│ │ ├── user.ts # 사용자 스키마
|
|
83
|
-
│ │ ├── post.ts # 게시글 스키마
|
|
84
|
-
│ │ └── common.ts # 공통 스키마 (pagination 등)
|
|
85
|
-
│ ├── services/ # 비즈니스 로직 레이어
|
|
86
|
-
│ │ ├── user/
|
|
87
|
-
│ │ │ ├── index.ts # re-export
|
|
88
|
-
│ │ │ ├── queries.ts # 조회 로직
|
|
89
|
-
│ │ │ └── mutations.ts # 생성/수정/삭제 로직
|
|
90
|
-
│ │ └── post/
|
|
91
|
-
│ │ ├── index.ts
|
|
92
|
-
│ │ ├── queries.ts
|
|
93
|
-
│ │ └── mutations.ts
|
|
94
|
-
│ ├── database/ # 데이터베이스 연결
|
|
95
|
-
│ │ └── prisma.ts # Prisma Client 싱글톤
|
|
96
|
-
│ ├── types/ # 타입 정의
|
|
97
|
-
│ │ ├── index.ts # re-export
|
|
98
|
-
│ │ ├── env.d.ts # 환경변수 타입
|
|
99
|
-
│ │ └── bindings.ts # Cloudflare Bindings 타입
|
|
100
|
-
│ └── lib/ # 공통 유틸리티
|
|
101
|
-
│ ├── errors.ts # 커스텀 에러 클래스
|
|
102
|
-
│ ├── jwt.ts # JWT 유틸리티
|
|
103
|
-
│ └── utils.ts # 범용 유틸리티
|
|
104
|
-
├── prisma/
|
|
105
|
-
│ ├── schema.prisma # Prisma 스키마
|
|
106
|
-
│ └── generated/ # Prisma Client 출력
|
|
107
|
-
│ └── client/
|
|
108
|
-
├── wrangler.toml # Cloudflare Workers 설정
|
|
109
|
-
├── .dev.vars # 로컬 환경변수
|
|
110
|
-
├── package.json
|
|
111
|
-
└── tsconfig.json
|
|
18
|
+
src/
|
|
19
|
+
├── index.ts # Entry point
|
|
20
|
+
├── routes/ # 라우트 모듈
|
|
21
|
+
├── middleware/ # 커스텀 미들웨어
|
|
22
|
+
├── validators/ # Zod 스키마
|
|
23
|
+
├── services/ # 비즈니스 로직
|
|
24
|
+
│ └── user/
|
|
25
|
+
│ ├── queries.ts # 조회
|
|
26
|
+
│ └── mutations.ts # 생성/수정/삭제
|
|
27
|
+
├── database/ # Prisma Client
|
|
28
|
+
├── types/ # 타입 정의
|
|
29
|
+
└── lib/ # 유틸리티
|
|
112
30
|
```
|
|
113
31
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
### 1. Entry Point (src/index.ts)
|
|
32
|
+
---
|
|
117
33
|
|
|
118
|
-
|
|
34
|
+
## Entry Point
|
|
119
35
|
|
|
120
36
|
```typescript
|
|
121
37
|
// src/index.ts
|
|
122
38
|
import { Hono } from 'hono'
|
|
123
39
|
import { logger } from 'hono/logger'
|
|
124
40
|
import { cors } from 'hono/cors'
|
|
125
|
-
import { secureHeaders } from 'hono/secure-headers'
|
|
126
41
|
import { HTTPException } from 'hono/http-exception'
|
|
127
42
|
import { users } from './routes/users'
|
|
128
|
-
import { posts } from './routes/posts'
|
|
129
|
-
import { auth } from './routes/auth'
|
|
130
|
-
|
|
131
|
-
type Bindings = {
|
|
132
|
-
DATABASE_URL: string
|
|
133
|
-
JWT_SECRET: string
|
|
134
|
-
NODE_ENV: string
|
|
135
|
-
}
|
|
136
43
|
|
|
137
|
-
type
|
|
138
|
-
|
|
139
|
-
}
|
|
44
|
+
type Bindings = { DATABASE_URL: string; JWT_SECRET: string }
|
|
45
|
+
type Variables = { userId: string }
|
|
140
46
|
|
|
141
47
|
const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()
|
|
142
48
|
|
|
143
|
-
// 글로벌 미들웨어
|
|
144
49
|
app.use(logger())
|
|
145
|
-
app.use(secureHeaders())
|
|
146
50
|
app.use('/api/*', cors())
|
|
147
51
|
|
|
148
|
-
// 글로벌 에러 핸들러
|
|
149
52
|
app.onError((err, c) => {
|
|
150
53
|
if (err instanceof HTTPException) {
|
|
151
54
|
return c.json({ error: err.message }, err.status)
|
|
152
55
|
}
|
|
153
|
-
console.error(err)
|
|
154
56
|
return c.json({ error: 'Internal Server Error' }, 500)
|
|
155
57
|
})
|
|
156
58
|
|
|
157
|
-
|
|
158
|
-
app.notFound((c) => {
|
|
159
|
-
return c.json({ error: 'Not Found', path: c.req.path }, 404)
|
|
160
|
-
})
|
|
59
|
+
app.notFound((c) => c.json({ error: 'Not Found' }, 404))
|
|
161
60
|
|
|
162
|
-
// 라우트 마운트
|
|
163
61
|
app.route('/api/users', users)
|
|
164
|
-
app.route('/api/posts', posts)
|
|
165
|
-
app.route('/api/auth', auth)
|
|
166
|
-
|
|
167
|
-
// Health Check
|
|
168
62
|
app.get('/health', (c) => c.json({ status: 'ok' }))
|
|
169
63
|
|
|
170
64
|
export default app
|
|
171
65
|
```
|
|
172
66
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
파일 기반으로 라우트를 모듈화하여 관리합니다.
|
|
176
|
-
|
|
177
|
-
```
|
|
178
|
-
routes/
|
|
179
|
-
├── index.ts # 라우트 통합
|
|
180
|
-
├── users.ts # /users 라우트
|
|
181
|
-
├── posts.ts # /posts 라우트
|
|
182
|
-
└── auth.ts # /auth 라우트
|
|
183
|
-
```
|
|
67
|
+
---
|
|
184
68
|
|
|
185
|
-
|
|
186
|
-
- 각 파일은 독립적인 Hono 인스턴스
|
|
187
|
-
- zValidator로 요청 검증
|
|
188
|
-
- 미들웨어 적용 가능
|
|
189
|
-
- RPC 타입 추론 지원
|
|
69
|
+
## Routes Layer
|
|
190
70
|
|
|
191
71
|
```typescript
|
|
192
72
|
// routes/users.ts
|
|
193
73
|
import { Hono } from 'hono'
|
|
194
74
|
import { zValidator } from '@hono/zod-validator'
|
|
195
75
|
import { authMiddleware } from '@/middleware/auth'
|
|
196
|
-
import { createUserSchema,
|
|
197
|
-
import {
|
|
198
|
-
import { getUsers, getUserById, createUser, updateUser, deleteUser } from '@/services/user'
|
|
76
|
+
import { createUserSchema, userIdSchema } from '@/validators/user'
|
|
77
|
+
import { getUsers, createUser } from '@/services/user'
|
|
199
78
|
|
|
200
79
|
const users = new Hono()
|
|
201
80
|
|
|
202
|
-
// 목록 조회 (공개)
|
|
203
81
|
users.get('/', zValidator('query', paginationSchema), async (c) => {
|
|
204
82
|
const { page, limit } = c.req.valid('query')
|
|
205
|
-
|
|
206
|
-
return c.json(result)
|
|
83
|
+
return c.json(await getUsers({ page, limit }))
|
|
207
84
|
})
|
|
208
85
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const user = await getUserById(id)
|
|
213
|
-
return c.json({ user })
|
|
86
|
+
users.post('/', authMiddleware, zValidator('json', createUserSchema), async (c) => {
|
|
87
|
+
const data = c.req.valid('json')
|
|
88
|
+
return c.json({ user: await createUser(data) }, 201)
|
|
214
89
|
})
|
|
215
90
|
|
|
216
|
-
// 생성 (인증 필요)
|
|
217
|
-
users.post(
|
|
218
|
-
'/',
|
|
219
|
-
authMiddleware,
|
|
220
|
-
zValidator('json', createUserSchema),
|
|
221
|
-
async (c) => {
|
|
222
|
-
const data = c.req.valid('json')
|
|
223
|
-
const user = await createUser(data)
|
|
224
|
-
return c.json({ user }, 201)
|
|
225
|
-
}
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
// 수정 (인증 필요)
|
|
229
|
-
users.put(
|
|
230
|
-
'/:id',
|
|
231
|
-
authMiddleware,
|
|
232
|
-
zValidator('param', userIdSchema),
|
|
233
|
-
zValidator('json', updateUserSchema),
|
|
234
|
-
async (c) => {
|
|
235
|
-
const { id } = c.req.valid('param')
|
|
236
|
-
const data = c.req.valid('json')
|
|
237
|
-
const user = await updateUser(id, data)
|
|
238
|
-
return c.json({ user })
|
|
239
|
-
}
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
// 삭제 (인증 필요)
|
|
243
|
-
users.delete(
|
|
244
|
-
'/:id',
|
|
245
|
-
authMiddleware,
|
|
246
|
-
zValidator('param', userIdSchema),
|
|
247
|
-
async (c) => {
|
|
248
|
-
const { id } = c.req.valid('param')
|
|
249
|
-
await deleteUser(id)
|
|
250
|
-
return c.json({ success: true })
|
|
251
|
-
}
|
|
252
|
-
)
|
|
253
|
-
|
|
254
91
|
export { users }
|
|
255
92
|
```
|
|
256
93
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
요청/응답 파이프라인을 처리합니다.
|
|
260
|
-
|
|
261
|
-
```
|
|
262
|
-
middleware/
|
|
263
|
-
├── index.ts # re-export
|
|
264
|
-
├── auth.ts # 인증 미들웨어
|
|
265
|
-
├── logger.ts # 로깅 미들웨어
|
|
266
|
-
└── rate-limit.ts # 레이트 리밋
|
|
267
|
-
```
|
|
94
|
+
---
|
|
268
95
|
|
|
269
|
-
|
|
270
|
-
- `createMiddleware`로 타입 안전 미들웨어 작성
|
|
271
|
-
- `HTTPException`으로 에러 처리
|
|
272
|
-
- `c.set()`으로 변수 전달
|
|
96
|
+
## Middleware Layer
|
|
273
97
|
|
|
274
98
|
```typescript
|
|
275
99
|
// middleware/auth.ts
|
|
276
100
|
import { createMiddleware } from 'hono/factory'
|
|
277
101
|
import { HTTPException } from 'hono/http-exception'
|
|
278
|
-
import { verifyToken } from '@/lib/jwt'
|
|
279
102
|
|
|
280
103
|
type Env = {
|
|
281
|
-
Bindings: {
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
Variables: {
|
|
285
|
-
userId: string
|
|
286
|
-
}
|
|
104
|
+
Bindings: { JWT_SECRET: string }
|
|
105
|
+
Variables: { userId: string }
|
|
287
106
|
}
|
|
288
107
|
|
|
289
108
|
export const authMiddleware = createMiddleware<Env>(async (c, next) => {
|
|
290
109
|
const token = c.req.header('Authorization')?.replace('Bearer ', '')
|
|
110
|
+
if (!token) throw new HTTPException(401, { message: 'Unauthorized' })
|
|
291
111
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
try {
|
|
297
|
-
const payload = await verifyToken(token, c.env.JWT_SECRET)
|
|
298
|
-
c.set('userId', payload.sub)
|
|
299
|
-
await next()
|
|
300
|
-
} catch {
|
|
301
|
-
throw new HTTPException(401, { message: 'Invalid token' })
|
|
302
|
-
}
|
|
112
|
+
const payload = await verifyToken(token, c.env.JWT_SECRET)
|
|
113
|
+
c.set('userId', payload.sub)
|
|
114
|
+
await next()
|
|
303
115
|
})
|
|
304
116
|
```
|
|
305
117
|
|
|
306
|
-
|
|
118
|
+
---
|
|
307
119
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
```
|
|
311
|
-
validators/
|
|
312
|
-
├── index.ts # re-export
|
|
313
|
-
├── user.ts # 사용자 스키마
|
|
314
|
-
├── post.ts # 게시글 스키마
|
|
315
|
-
└── common.ts # 공통 스키마
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
**Zod v4 문법**:
|
|
120
|
+
## Validators Layer
|
|
319
121
|
|
|
320
122
|
```typescript
|
|
321
123
|
// validators/user.ts
|
|
322
124
|
import { z } from 'zod'
|
|
323
125
|
|
|
324
|
-
// ✅ Zod v4 문법
|
|
325
126
|
export const createUserSchema = z.object({
|
|
326
|
-
email: z.email(),
|
|
327
|
-
name: z.string().min(1).
|
|
127
|
+
email: z.email(), // Zod v4
|
|
128
|
+
name: z.string().min(1).trim(),
|
|
328
129
|
password: z.string().min(8),
|
|
329
|
-
website: z.url().optional(), // ✅ v4: z.url()
|
|
330
130
|
})
|
|
331
131
|
|
|
332
|
-
export const updateUserSchema = createUserSchema.partial().omit({ password: true })
|
|
333
|
-
|
|
334
|
-
export const userIdSchema = z.object({
|
|
335
|
-
id: z.string().uuid(),
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
// 타입 추출
|
|
339
|
-
export type CreateUserInput = z.infer<typeof createUserSchema>
|
|
340
|
-
export type UpdateUserInput = z.infer<typeof updateUserSchema>
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
```typescript
|
|
344
|
-
// validators/common.ts
|
|
345
|
-
import { z } from 'zod'
|
|
346
|
-
|
|
347
132
|
export const paginationSchema = z.object({
|
|
348
|
-
page: z.coerce.number().positive().
|
|
349
|
-
limit: z.coerce.number().
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
export const searchSchema = z.object({
|
|
353
|
-
q: z.string().min(1),
|
|
354
|
-
page: z.coerce.number().positive().optional(),
|
|
355
|
-
limit: z.coerce.number().max(100).optional(),
|
|
133
|
+
page: z.coerce.number().positive().default(1),
|
|
134
|
+
limit: z.coerce.number().max(100).default(10),
|
|
356
135
|
})
|
|
357
136
|
|
|
358
|
-
export type
|
|
137
|
+
export type CreateUserInput = z.infer<typeof createUserSchema>
|
|
359
138
|
```
|
|
360
139
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
비즈니스 로직을 라우트와 분리하여 관리합니다.
|
|
140
|
+
---
|
|
364
141
|
|
|
365
|
-
|
|
366
|
-
services/
|
|
367
|
-
├── user/
|
|
368
|
-
│ ├── index.ts # re-export
|
|
369
|
-
│ ├── queries.ts # 조회 로직
|
|
370
|
-
│ └── mutations.ts # 생성/수정/삭제 로직
|
|
371
|
-
└── post/
|
|
372
|
-
├── index.ts
|
|
373
|
-
├── queries.ts
|
|
374
|
-
└── mutations.ts
|
|
375
|
-
```
|
|
142
|
+
## Services Layer
|
|
376
143
|
|
|
377
|
-
|
|
144
|
+
### Queries
|
|
378
145
|
|
|
379
146
|
```typescript
|
|
380
147
|
// services/user/queries.ts
|
|
381
148
|
import { HTTPException } from 'hono/http-exception'
|
|
382
149
|
import { prisma } from '@/database/prisma'
|
|
383
|
-
import type { PaginationInput } from '@/validators/common'
|
|
384
150
|
|
|
385
|
-
export const getUsers = async ({ page, limit }
|
|
151
|
+
export const getUsers = async ({ page, limit }) => {
|
|
386
152
|
const [users, total] = await Promise.all([
|
|
387
153
|
prisma.user.findMany({
|
|
388
154
|
skip: (page - 1) * limit,
|
|
389
155
|
take: limit,
|
|
390
|
-
|
|
391
|
-
select: {
|
|
392
|
-
id: true,
|
|
393
|
-
email: true,
|
|
394
|
-
name: true,
|
|
395
|
-
createdAt: true,
|
|
396
|
-
},
|
|
156
|
+
select: { id: true, email: true, name: true },
|
|
397
157
|
}),
|
|
398
158
|
prisma.user.count(),
|
|
399
159
|
])
|
|
400
|
-
|
|
401
|
-
return {
|
|
402
|
-
users,
|
|
403
|
-
pagination: {
|
|
404
|
-
page,
|
|
405
|
-
limit,
|
|
406
|
-
total,
|
|
407
|
-
totalPages: Math.ceil(total / limit),
|
|
408
|
-
},
|
|
409
|
-
}
|
|
160
|
+
return { users, pagination: { page, limit, total } }
|
|
410
161
|
}
|
|
411
162
|
|
|
412
163
|
export const getUserById = async (id: string) => {
|
|
413
|
-
const user = await prisma.user.findUnique({
|
|
414
|
-
|
|
415
|
-
include: { posts: true },
|
|
416
|
-
})
|
|
417
|
-
|
|
418
|
-
if (!user) {
|
|
419
|
-
throw new HTTPException(404, { message: 'User not found' })
|
|
420
|
-
}
|
|
421
|
-
|
|
164
|
+
const user = await prisma.user.findUnique({ where: { id } })
|
|
165
|
+
if (!user) throw new HTTPException(404, { message: 'User not found' })
|
|
422
166
|
return user
|
|
423
167
|
}
|
|
424
168
|
```
|
|
425
169
|
|
|
426
|
-
|
|
170
|
+
### Mutations
|
|
427
171
|
|
|
428
172
|
```typescript
|
|
429
173
|
// services/user/mutations.ts
|
|
430
174
|
import { HTTPException } from 'hono/http-exception'
|
|
431
175
|
import { prisma } from '@/database/prisma'
|
|
432
|
-
import type { CreateUserInput, UpdateUserInput } from '@/validators/user'
|
|
433
176
|
|
|
434
177
|
export const createUser = async (data: CreateUserInput) => {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
where: { email: data.email },
|
|
438
|
-
})
|
|
439
|
-
|
|
440
|
-
if (existing) {
|
|
441
|
-
throw new HTTPException(409, { message: 'Email already exists' })
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// 비밀번호 해싱 (실제로는 bcrypt 사용)
|
|
445
|
-
const hashedPassword = await hashPassword(data.password)
|
|
178
|
+
const existing = await prisma.user.findUnique({ where: { email: data.email } })
|
|
179
|
+
if (existing) throw new HTTPException(409, { message: 'Email already exists' })
|
|
446
180
|
|
|
447
181
|
return prisma.user.create({
|
|
448
|
-
data: {
|
|
449
|
-
|
|
450
|
-
password: hashedPassword,
|
|
451
|
-
},
|
|
452
|
-
select: {
|
|
453
|
-
id: true,
|
|
454
|
-
email: true,
|
|
455
|
-
name: true,
|
|
456
|
-
createdAt: true,
|
|
457
|
-
},
|
|
182
|
+
data: { ...data, password: await hashPassword(data.password) },
|
|
183
|
+
select: { id: true, email: true, name: true },
|
|
458
184
|
})
|
|
459
185
|
}
|
|
460
|
-
|
|
461
|
-
export const updateUser = async (id: string, data: UpdateUserInput) => {
|
|
462
|
-
const user = await prisma.user.findUnique({ where: { id } })
|
|
463
|
-
|
|
464
|
-
if (!user) {
|
|
465
|
-
throw new HTTPException(404, { message: 'User not found' })
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return prisma.user.update({
|
|
469
|
-
where: { id },
|
|
470
|
-
data,
|
|
471
|
-
select: {
|
|
472
|
-
id: true,
|
|
473
|
-
email: true,
|
|
474
|
-
name: true,
|
|
475
|
-
updatedAt: true,
|
|
476
|
-
},
|
|
477
|
-
})
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
export const deleteUser = async (id: string) => {
|
|
481
|
-
const user = await prisma.user.findUnique({ where: { id } })
|
|
482
|
-
|
|
483
|
-
if (!user) {
|
|
484
|
-
throw new HTTPException(404, { message: 'User not found' })
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
await prisma.user.delete({ where: { id } })
|
|
488
|
-
}
|
|
489
186
|
```
|
|
490
187
|
|
|
491
|
-
|
|
188
|
+
---
|
|
492
189
|
|
|
493
|
-
|
|
190
|
+
## Database Layer
|
|
494
191
|
|
|
495
192
|
```typescript
|
|
496
193
|
// database/prisma.ts
|
|
497
194
|
import { PrismaClient } from '../../prisma/generated/client'
|
|
498
195
|
|
|
499
|
-
const globalForPrisma = globalThis as
|
|
500
|
-
prisma: PrismaClient | undefined
|
|
501
|
-
}
|
|
196
|
+
const globalForPrisma = globalThis as { prisma?: PrismaClient }
|
|
502
197
|
|
|
503
|
-
export const prisma =
|
|
504
|
-
globalForPrisma.prisma ??
|
|
505
|
-
new PrismaClient({
|
|
506
|
-
log: process.env.NODE_ENV === 'development' ? ['query'] : [],
|
|
507
|
-
})
|
|
198
|
+
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
|
|
508
199
|
|
|
509
200
|
if (process.env.NODE_ENV !== 'production') {
|
|
510
201
|
globalForPrisma.prisma = prisma
|
|
511
202
|
}
|
|
512
203
|
```
|
|
513
204
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
### Query Flow (읽기)
|
|
517
|
-
|
|
518
|
-
```
|
|
519
|
-
1. Client sends GET request
|
|
520
|
-
│
|
|
521
|
-
▼
|
|
522
|
-
┌─────────────────┐
|
|
523
|
-
│ Middleware │ ← logger, cors, secureHeaders
|
|
524
|
-
│ Stack │
|
|
525
|
-
└────────┬────────┘
|
|
526
|
-
│
|
|
527
|
-
▼
|
|
528
|
-
┌─────────────────┐
|
|
529
|
-
│ Route Handler │ ← zValidator('query', schema)
|
|
530
|
-
└────────┬────────┘
|
|
531
|
-
│
|
|
532
|
-
▼
|
|
533
|
-
┌─────────────────┐
|
|
534
|
-
│ Service Query │ ← getUsers(), getUserById()
|
|
535
|
-
└────────┬────────┘
|
|
536
|
-
│
|
|
537
|
-
▼
|
|
538
|
-
┌─────────────────┐
|
|
539
|
-
│ Prisma Client │ ← findMany(), findUnique()
|
|
540
|
-
└────────┬────────┘
|
|
541
|
-
│
|
|
542
|
-
▼
|
|
543
|
-
┌─────────────────┐
|
|
544
|
-
│ Database │
|
|
545
|
-
└─────────────────┘
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
### Mutation Flow (쓰기)
|
|
549
|
-
|
|
550
|
-
```
|
|
551
|
-
1. Client sends POST/PUT/DELETE request
|
|
552
|
-
│
|
|
553
|
-
▼
|
|
554
|
-
┌─────────────────┐
|
|
555
|
-
│ Middleware │ ← authMiddleware (인증)
|
|
556
|
-
│ Stack │
|
|
557
|
-
└────────┬────────┘
|
|
558
|
-
│
|
|
559
|
-
▼
|
|
560
|
-
┌─────────────────┐
|
|
561
|
-
│ Zod Validation │ ← zValidator('json', schema)
|
|
562
|
-
└────────┬────────┘
|
|
563
|
-
│ Valid
|
|
564
|
-
▼
|
|
565
|
-
┌─────────────────┐
|
|
566
|
-
│ Service │ ← createUser(), updateUser()
|
|
567
|
-
│ Mutation │
|
|
568
|
-
└────────┬────────┘
|
|
569
|
-
│
|
|
570
|
-
▼
|
|
571
|
-
┌─────────────────┐
|
|
572
|
-
│ Business Logic │ ← 중복 체크, 권한 검증
|
|
573
|
-
└────────┬────────┘
|
|
574
|
-
│
|
|
575
|
-
▼
|
|
576
|
-
┌─────────────────┐
|
|
577
|
-
│ Prisma Mutation│ ← create(), update(), delete()
|
|
578
|
-
└────────┬────────┘
|
|
579
|
-
│
|
|
580
|
-
▼
|
|
581
|
-
┌─────────────────┐
|
|
582
|
-
│ Database │
|
|
583
|
-
└─────────────────┘
|
|
584
|
-
```
|
|
585
|
-
|
|
586
|
-
### Error Flow
|
|
587
|
-
|
|
588
|
-
```
|
|
589
|
-
1. Error occurs anywhere
|
|
590
|
-
│
|
|
591
|
-
▼
|
|
592
|
-
┌─────────────────┐
|
|
593
|
-
│ HTTPException │ ← 비즈니스 에러
|
|
594
|
-
│ throw │
|
|
595
|
-
└────────┬────────┘
|
|
596
|
-
│
|
|
597
|
-
▼
|
|
598
|
-
┌─────────────────┐
|
|
599
|
-
│ app.onError() │ ← 글로벌 에러 핸들러
|
|
600
|
-
└────────┬────────┘
|
|
601
|
-
│
|
|
602
|
-
▼
|
|
603
|
-
┌─────────────────┐
|
|
604
|
-
│ JSON Response │ ← { error: message }
|
|
605
|
-
└─────────────────┘
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
## RPC Pattern (Type-safe Client)
|
|
205
|
+
---
|
|
609
206
|
|
|
610
|
-
|
|
207
|
+
## RPC Pattern
|
|
611
208
|
|
|
612
209
|
```typescript
|
|
613
|
-
//
|
|
210
|
+
// server
|
|
614
211
|
const app = new Hono()
|
|
615
212
|
.route('/api/users', users)
|
|
616
|
-
.route('/api/posts', posts)
|
|
617
213
|
|
|
618
214
|
export type AppType = typeof app
|
|
619
|
-
export default app
|
|
620
|
-
```
|
|
621
215
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
```typescript
|
|
625
|
-
// client.ts
|
|
216
|
+
// client
|
|
626
217
|
import { hc } from 'hono/client'
|
|
627
218
|
import type { AppType } from './server'
|
|
628
219
|
|
|
629
220
|
const client = hc<AppType>('http://localhost:8787')
|
|
630
221
|
|
|
631
|
-
|
|
632
|
-
const
|
|
633
|
-
const res = await client.api.users.$get({
|
|
634
|
-
query: { page: '1', limit: '10' },
|
|
635
|
-
})
|
|
636
|
-
return res.json()
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
const createUser = async (data: CreateUserInput) => {
|
|
640
|
-
const res = await client.api.users.$post({
|
|
641
|
-
json: data,
|
|
642
|
-
})
|
|
643
|
-
return res.json()
|
|
644
|
-
}
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
## Technology Stack
|
|
648
|
-
|
|
649
|
-
### Core Framework
|
|
650
|
-
|
|
651
|
-
| Technology | Purpose | Version |
|
|
652
|
-
|------------|---------|---------|
|
|
653
|
-
| Hono | Web Standards 서버 프레임워크 | latest |
|
|
654
|
-
| TypeScript | 타입 안전 개발 | 5.x |
|
|
655
|
-
|
|
656
|
-
### Validation
|
|
657
|
-
|
|
658
|
-
| Technology | Purpose | Version |
|
|
659
|
-
|------------|---------|---------|
|
|
660
|
-
| Zod | 스키마 검증 | **4.x** |
|
|
661
|
-
| @hono/zod-validator | Zod 미들웨어 | latest |
|
|
662
|
-
|
|
663
|
-
### Database
|
|
664
|
-
|
|
665
|
-
| Technology | Purpose | Version |
|
|
666
|
-
|------------|---------|---------|
|
|
667
|
-
| Prisma | ORM | **7.x** |
|
|
668
|
-
| PostgreSQL | Primary Database | - |
|
|
669
|
-
| Cloudflare D1 | Edge Database | - |
|
|
670
|
-
|
|
671
|
-
### Runtime
|
|
672
|
-
|
|
673
|
-
| Technology | Purpose |
|
|
674
|
-
|------------|---------|
|
|
675
|
-
| Cloudflare Workers | Edge Runtime |
|
|
676
|
-
| Node.js | 로컬 개발 |
|
|
677
|
-
| Bun | 대안 런타임 |
|
|
678
|
-
|
|
679
|
-
## TypeScript Configuration
|
|
680
|
-
|
|
681
|
-
```json
|
|
682
|
-
{
|
|
683
|
-
"compilerOptions": {
|
|
684
|
-
"target": "ES2022",
|
|
685
|
-
"lib": ["ES2022"],
|
|
686
|
-
"module": "ESNext",
|
|
687
|
-
"moduleResolution": "bundler",
|
|
688
|
-
"jsx": "react-jsx",
|
|
689
|
-
"jsxImportSource": "hono/jsx",
|
|
690
|
-
"strict": true,
|
|
691
|
-
"noUnusedLocals": true,
|
|
692
|
-
"noUnusedParameters": true,
|
|
693
|
-
"types": ["@cloudflare/workers-types"],
|
|
694
|
-
"paths": {
|
|
695
|
-
"@/*": ["./src/*"]
|
|
696
|
-
}
|
|
697
|
-
},
|
|
698
|
-
"include": ["src/**/*"]
|
|
699
|
-
}
|
|
700
|
-
```
|
|
701
|
-
|
|
702
|
-
## Deployment Architecture
|
|
703
|
-
|
|
704
|
-
### Cloudflare Workers
|
|
705
|
-
|
|
706
|
-
```
|
|
707
|
-
┌─────────────────────────────────────┐
|
|
708
|
-
│ Cloudflare Edge Network │
|
|
709
|
-
│ │
|
|
710
|
-
│ ┌─────────────┐ ┌─────────────┐ │
|
|
711
|
-
│ │ Workers │ │ KV │ │
|
|
712
|
-
│ │ (Hono App) │ │ (Cache) │ │
|
|
713
|
-
│ └──────┬──────┘ └─────────────┘ │
|
|
714
|
-
│ │ │
|
|
715
|
-
│ ┌──────▼──────┐ ┌─────────────┐ │
|
|
716
|
-
│ │ D1 │ │ R2 │ │
|
|
717
|
-
│ │ (Database) │ │ (Storage) │ │
|
|
718
|
-
│ └─────────────┘ └─────────────┘ │
|
|
719
|
-
│ │
|
|
720
|
-
└─────────────────────────────────────┘
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
### Node.js / Bun
|
|
724
|
-
|
|
725
|
-
```
|
|
726
|
-
┌─────────────────────────────────────┐
|
|
727
|
-
│ Server (Node/Bun) │
|
|
728
|
-
│ │
|
|
729
|
-
│ ┌─────────────────────────────┐ │
|
|
730
|
-
│ │ Hono App │ │
|
|
731
|
-
│ │ (Full-stack Server) │ │
|
|
732
|
-
│ └─────────────────────────────┘ │
|
|
733
|
-
│ │
|
|
734
|
-
└───────────────────┬─────────────────┘
|
|
735
|
-
│
|
|
736
|
-
▼
|
|
737
|
-
┌───────────────┐
|
|
738
|
-
│ Database │
|
|
739
|
-
│ (PostgreSQL) │
|
|
740
|
-
└───────────────┘
|
|
741
|
-
```
|
|
742
|
-
|
|
743
|
-
## Documentation Structure
|
|
744
|
-
|
|
745
|
-
```
|
|
746
|
-
docs/
|
|
747
|
-
├── library/ # 라이브러리 레퍼런스
|
|
748
|
-
│ ├── hono/ # Hono 가이드
|
|
749
|
-
│ │ ├── index.md # 기본 사용법
|
|
750
|
-
│ │ ├── middleware.md # 미들웨어
|
|
751
|
-
│ │ ├── validation.md # Zod 검증
|
|
752
|
-
│ │ ├── error-handling.md # 에러 처리
|
|
753
|
-
│ │ └── rpc.md # RPC Client
|
|
754
|
-
│ ├── prisma/ # Prisma 가이드
|
|
755
|
-
│ │ ├── index.md # 기본 사용법
|
|
756
|
-
│ │ └── cloudflare-d1.md # D1 연동
|
|
757
|
-
│ └── zod/ # Zod 가이드
|
|
758
|
-
│ └── index.md
|
|
759
|
-
├── deployment/ # 배포 가이드
|
|
760
|
-
│ ├── index.md # 배포 개요
|
|
761
|
-
│ └── cloudflare.md # Cloudflare 배포
|
|
762
|
-
├── mcp/ # MCP 도구 가이드
|
|
763
|
-
│ ├── index.md
|
|
764
|
-
│ ├── sgrep.md
|
|
765
|
-
│ ├── sequential-thinking.md
|
|
766
|
-
│ └── context7.md
|
|
767
|
-
├── skills/ # 스킬 가이드
|
|
768
|
-
│ └── gemini-review/
|
|
769
|
-
├── git/ # Git 규칙
|
|
770
|
-
│ └── index.md
|
|
771
|
-
└── architecture/ # 아키텍처
|
|
772
|
-
└── architecture.md # 시스템 아키텍처
|
|
773
|
-
```
|
|
774
|
-
|
|
775
|
-
## Security Considerations
|
|
776
|
-
|
|
777
|
-
### Input Validation
|
|
778
|
-
|
|
779
|
-
모든 라우트에서 Zod를 통한 입력 검증:
|
|
780
|
-
|
|
781
|
-
```typescript
|
|
782
|
-
// ✅ 올바른 패턴: zValidator 사용
|
|
783
|
-
app.post(
|
|
784
|
-
'/users',
|
|
785
|
-
zValidator('json', createUserSchema), // 자동 검증
|
|
786
|
-
(c) => {
|
|
787
|
-
const data = c.req.valid('json') // data는 이미 검증됨
|
|
788
|
-
// ...
|
|
789
|
-
}
|
|
790
|
-
)
|
|
791
|
-
|
|
792
|
-
// ❌ 잘못된 패턴: 수동 검증 금지
|
|
793
|
-
app.post('/users', async (c) => {
|
|
794
|
-
const body = await c.req.json()
|
|
795
|
-
if (!body.email) { // ❌ 이렇게 하지 마세요
|
|
796
|
-
return c.json({ error: 'Email required' }, 400)
|
|
797
|
-
}
|
|
798
|
-
})
|
|
799
|
-
```
|
|
800
|
-
|
|
801
|
-
### Error Handling
|
|
802
|
-
|
|
803
|
-
HTTPException을 통한 안전한 에러 처리:
|
|
804
|
-
|
|
805
|
-
```typescript
|
|
806
|
-
// ✅ 올바른 패턴
|
|
807
|
-
import { HTTPException } from 'hono/http-exception'
|
|
808
|
-
|
|
809
|
-
if (!user) {
|
|
810
|
-
throw new HTTPException(404, { message: 'User not found' })
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// ❌ 잘못된 패턴
|
|
814
|
-
if (!user) {
|
|
815
|
-
throw new Error('User not found') // ❌ HTTPException 사용해야 함
|
|
816
|
-
}
|
|
222
|
+
const res = await client.api.users.$get({ query: { page: '1' } })
|
|
223
|
+
const data = await res.json()
|
|
817
224
|
```
|
|
818
225
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
미들웨어를 통한 인증 처리:
|
|
226
|
+
---
|
|
822
227
|
|
|
823
|
-
|
|
824
|
-
// ✅ 올바른 패턴: 미들웨어 사용
|
|
825
|
-
app.post('/protected', authMiddleware, (c) => {
|
|
826
|
-
const userId = c.get('userId')
|
|
827
|
-
// ...
|
|
828
|
-
})
|
|
228
|
+
## Tech Stack
|
|
829
229
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
230
|
+
| 분류 | 기술 | 버전 |
|
|
231
|
+
|------|------|------|
|
|
232
|
+
| Framework | Hono | latest |
|
|
233
|
+
| Validation | Zod | **4.x** |
|
|
234
|
+
| ORM | Prisma | **7.x** |
|
|
235
|
+
| Runtime | Cloudflare Workers, Node.js, Bun | - |
|
|
836
236
|
|
|
837
|
-
|
|
237
|
+
---
|
|
838
238
|
|
|
839
|
-
|
|
840
|
-
// types/env.d.ts
|
|
841
|
-
type Bindings = {
|
|
842
|
-
DATABASE_URL: string
|
|
843
|
-
JWT_SECRET: string
|
|
844
|
-
NODE_ENV: 'development' | 'production'
|
|
845
|
-
}
|
|
239
|
+
## 패턴 요약
|
|
846
240
|
|
|
847
|
-
|
|
848
|
-
|
|
241
|
+
| 레이어 | 역할 | 파일 |
|
|
242
|
+
|--------|------|------|
|
|
243
|
+
| Routes | HTTP 라우팅 | `routes/*.ts` |
|
|
244
|
+
| Middleware | 요청/응답 처리 | `middleware/*.ts` |
|
|
245
|
+
| Validators | 입력 검증 | `validators/*.ts` |
|
|
246
|
+
| Services | 비즈니스 로직 | `services/*/*.ts` |
|
|
247
|
+
| Database | 데이터 액세스 | `database/prisma.ts` |
|
|
849
248
|
|
|
850
|
-
|
|
851
|
-
const secret = c.env.JWT_SECRET // 타입 안전
|
|
852
|
-
})
|
|
853
|
-
```
|
|
854
|
-
|
|
855
|
-
## Performance Considerations
|
|
856
|
-
|
|
857
|
-
### Middleware Optimization
|
|
858
|
-
|
|
859
|
-
```typescript
|
|
860
|
-
// 필요한 경로에만 미들웨어 적용
|
|
861
|
-
app.use('/api/*', cors()) // API 전용
|
|
862
|
-
app.use('/api/protected/*', authMiddleware) // 인증 필요 경로만
|
|
863
|
-
|
|
864
|
-
// 불필요한 미들웨어 중복 적용 금지
|
|
865
|
-
```
|
|
866
|
-
|
|
867
|
-
### Database Optimization
|
|
868
|
-
|
|
869
|
-
```typescript
|
|
870
|
-
// 필요한 필드만 선택
|
|
871
|
-
const users = await prisma.user.findMany({
|
|
872
|
-
select: { // ✅ select 사용
|
|
873
|
-
id: true,
|
|
874
|
-
name: true,
|
|
875
|
-
email: true,
|
|
876
|
-
},
|
|
877
|
-
take: 20, // ✅ 페이지네이션
|
|
878
|
-
skip: (page - 1) * 20,
|
|
879
|
-
})
|
|
880
|
-
|
|
881
|
-
// 병렬 쿼리 실행
|
|
882
|
-
const [users, total] = await Promise.all([
|
|
883
|
-
prisma.user.findMany({ take: 20 }),
|
|
884
|
-
prisma.user.count(),
|
|
885
|
-
])
|
|
886
|
-
```
|
|
887
|
-
|
|
888
|
-
### Response Optimization
|
|
889
|
-
|
|
890
|
-
```typescript
|
|
891
|
-
// 적절한 상태 코드 사용
|
|
892
|
-
app.post('/users', (c) => {
|
|
893
|
-
return c.json({ user }, 201) // Created
|
|
894
|
-
})
|
|
895
|
-
|
|
896
|
-
// 불필요한 데이터 제외
|
|
897
|
-
const { password, ...safeUser } = user
|
|
898
|
-
return c.json({ user: safeUser })
|
|
899
|
-
```
|
|
249
|
+
---
|
|
900
250
|
|
|
901
|
-
##
|
|
251
|
+
## 관련 문서
|
|
902
252
|
|
|
903
|
-
- [Hono
|
|
904
|
-
- [
|
|
905
|
-
- [Zod 검증](../library/hono/validation.md)
|
|
906
|
-
- [에러 처리](../library/hono/error-handling.md)
|
|
907
|
-
- [RPC Client](../library/hono/rpc.md)
|
|
908
|
-
- [Prisma v7](../library/prisma/index.md)
|
|
253
|
+
- [Hono](../library/hono/index.md)
|
|
254
|
+
- [Prisma](../library/prisma/index.md)
|
|
909
255
|
- [Cloudflare 배포](../deployment/cloudflare.md)
|