@prmichaelsen/acp-visualizer 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/README.md +68 -0
- package/agent/commands/acp.clarification-address.md +417 -0
- package/agent/commands/acp.clarification-capture.md +386 -0
- package/agent/commands/acp.clarification-create.md +437 -0
- package/agent/commands/acp.clarifications-research.md +326 -0
- package/agent/commands/acp.command-create.md +432 -0
- package/agent/commands/acp.design-create.md +286 -0
- package/agent/commands/acp.design-reference.md +355 -0
- package/agent/commands/acp.handoff.md +270 -0
- package/agent/commands/acp.index.md +423 -0
- package/agent/commands/acp.init.md +546 -0
- package/agent/commands/acp.package-create.md +895 -0
- package/agent/commands/acp.package-info.md +212 -0
- package/agent/commands/acp.package-install.md +539 -0
- package/agent/commands/acp.package-list.md +280 -0
- package/agent/commands/acp.package-publish.md +541 -0
- package/agent/commands/acp.package-remove.md +293 -0
- package/agent/commands/acp.package-search.md +307 -0
- package/agent/commands/acp.package-update.md +361 -0
- package/agent/commands/acp.package-validate.md +540 -0
- package/agent/commands/acp.pattern-create.md +386 -0
- package/agent/commands/acp.plan.md +587 -0
- package/agent/commands/acp.proceed.md +882 -0
- package/agent/commands/acp.project-create.md +675 -0
- package/agent/commands/acp.project-info.md +312 -0
- package/agent/commands/acp.project-list.md +226 -0
- package/agent/commands/acp.project-remove.md +379 -0
- package/agent/commands/acp.project-set.md +227 -0
- package/agent/commands/acp.project-update.md +307 -0
- package/agent/commands/acp.projects-restore.md +228 -0
- package/agent/commands/acp.projects-sync.md +347 -0
- package/agent/commands/acp.report.md +407 -0
- package/agent/commands/acp.resume.md +239 -0
- package/agent/commands/acp.sessions.md +301 -0
- package/agent/commands/acp.status.md +293 -0
- package/agent/commands/acp.sync.md +364 -0
- package/agent/commands/acp.task-create.md +500 -0
- package/agent/commands/acp.update.md +302 -0
- package/agent/commands/acp.validate.md +466 -0
- package/agent/commands/acp.version-check-for-updates.md +276 -0
- package/agent/commands/acp.version-check.md +191 -0
- package/agent/commands/acp.version-update.md +289 -0
- package/agent/commands/command.template.md +339 -0
- package/agent/commands/git.commit.md +526 -0
- package/agent/commands/git.init.md +514 -0
- package/agent/commands/tanstack-cloudflare.deploy.md +272 -0
- package/agent/commands/tanstack-cloudflare.tail.md +275 -0
- package/agent/design/.gitkeep +0 -0
- package/agent/design/design.template.md +154 -0
- package/agent/design/local.dashboard-layout-routing.md +288 -0
- package/agent/design/local.data-model-yaml-parsing.md +310 -0
- package/agent/design/local.search-filtering.md +331 -0
- package/agent/design/local.server-api-auto-refresh.md +235 -0
- package/agent/design/local.table-tree-views.md +299 -0
- package/agent/design/local.visualizer-requirements.md +349 -0
- package/agent/design/requirements.template.md +387 -0
- package/agent/index/.gitkeep +0 -0
- package/agent/index/acp.core.yaml +137 -0
- package/agent/index/local.main.template.yaml +37 -0
- package/agent/manifest.template.yaml +13 -0
- package/agent/manifest.yaml +302 -0
- package/agent/milestones/.gitkeep +0 -0
- package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +67 -0
- package/agent/milestones/milestone-1-{title}.template.md +206 -0
- package/agent/milestones/milestone-2-dashboard-views-interaction.md +79 -0
- package/agent/package.template.yaml +86 -0
- package/agent/patterns/.gitkeep +0 -0
- package/agent/patterns/bootstrap.template.md +1237 -0
- package/agent/patterns/pattern.template.md +382 -0
- package/agent/patterns/tanstack-cloudflare.acl-permissions.md +332 -0
- package/agent/patterns/tanstack-cloudflare.action-bar-item.md +416 -0
- package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +401 -0
- package/agent/patterns/tanstack-cloudflare.auth-session-management.md +387 -0
- package/agent/patterns/tanstack-cloudflare.card-and-list.md +271 -0
- package/agent/patterns/tanstack-cloudflare.chat-engine.md +353 -0
- package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +346 -0
- package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +516 -0
- package/agent/patterns/tanstack-cloudflare.email-service.md +431 -0
- package/agent/patterns/tanstack-cloudflare.expander.md +98 -0
- package/agent/patterns/tanstack-cloudflare.fcm-push.md +115 -0
- package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +441 -0
- package/agent/patterns/tanstack-cloudflare.firebase-auth.md +348 -0
- package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +550 -0
- package/agent/patterns/tanstack-cloudflare.firebase-storage.md +369 -0
- package/agent/patterns/tanstack-cloudflare.form-controls.md +145 -0
- package/agent/patterns/tanstack-cloudflare.global-search-context.md +93 -0
- package/agent/patterns/tanstack-cloudflare.image-carousel.md +126 -0
- package/agent/patterns/tanstack-cloudflare.library-services.md +553 -0
- package/agent/patterns/tanstack-cloudflare.lightbox.md +169 -0
- package/agent/patterns/tanstack-cloudflare.markdown-content.md +115 -0
- package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +98 -0
- package/agent/patterns/tanstack-cloudflare.modal.md +156 -0
- package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +461 -0
- package/agent/patterns/tanstack-cloudflare.notifications-engine.md +151 -0
- package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +90 -0
- package/agent/patterns/tanstack-cloudflare.og-metadata.md +296 -0
- package/agent/patterns/tanstack-cloudflare.pagination.md +442 -0
- package/agent/patterns/tanstack-cloudflare.pill-input.md +220 -0
- package/agent/patterns/tanstack-cloudflare.provider-adapter.md +401 -0
- package/agent/patterns/tanstack-cloudflare.rate-limiting.md +323 -0
- package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +338 -0
- package/agent/patterns/tanstack-cloudflare.searchable-settings.md +375 -0
- package/agent/patterns/tanstack-cloudflare.slide-over.md +129 -0
- package/agent/patterns/tanstack-cloudflare.ssr-preload.md +571 -0
- package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +508 -0
- package/agent/patterns/tanstack-cloudflare.toast-system.md +142 -0
- package/agent/patterns/tanstack-cloudflare.unified-header.md +280 -0
- package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +628 -0
- package/agent/patterns/tanstack-cloudflare.websocket-manager.md +237 -0
- package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +358 -0
- package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +336 -0
- package/agent/progress.template.yaml +161 -0
- package/agent/progress.yaml +145 -0
- package/agent/schemas/package.schema.yaml +276 -0
- package/agent/scripts/acp.common.sh +1781 -0
- package/agent/scripts/acp.install.sh +333 -0
- package/agent/scripts/acp.package-create.sh +924 -0
- package/agent/scripts/acp.package-info.sh +288 -0
- package/agent/scripts/acp.package-install.sh +893 -0
- package/agent/scripts/acp.package-list.sh +311 -0
- package/agent/scripts/acp.package-publish.sh +420 -0
- package/agent/scripts/acp.package-remove.sh +348 -0
- package/agent/scripts/acp.package-search.sh +156 -0
- package/agent/scripts/acp.package-update.sh +517 -0
- package/agent/scripts/acp.package-validate.sh +1018 -0
- package/agent/scripts/acp.uninstall.sh +85 -0
- package/agent/scripts/acp.version-check-for-updates.sh +98 -0
- package/agent/scripts/acp.version-check.sh +47 -0
- package/agent/scripts/acp.version-update.sh +176 -0
- package/agent/scripts/acp.yaml-parser.sh +985 -0
- package/agent/scripts/acp.yaml-validate.sh +205 -0
- package/agent/tasks/.gitkeep +0 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +210 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +294 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +193 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +262 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +156 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +178 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +141 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +153 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +174 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +233 -0
- package/agent/tasks/task-1-{title}.template.md +244 -0
- package/bin/visualize.mjs +84 -0
- package/package.json +48 -0
- package/src/components/ExtraFieldsBadge.tsx +15 -0
- package/src/components/FilterBar.tsx +33 -0
- package/src/components/Header.tsx +23 -0
- package/src/components/MilestoneTable.tsx +167 -0
- package/src/components/MilestoneTree.tsx +84 -0
- package/src/components/ProgressBar.tsx +20 -0
- package/src/components/SearchInput.tsx +22 -0
- package/src/components/Sidebar.tsx +54 -0
- package/src/components/StatusBadge.tsx +23 -0
- package/src/components/StatusDot.tsx +12 -0
- package/src/components/TaskList.tsx +36 -0
- package/src/components/ViewToggle.tsx +31 -0
- package/src/lib/config.ts +8 -0
- package/src/lib/file-watcher.ts +43 -0
- package/src/lib/search.ts +48 -0
- package/src/lib/types.ts +73 -0
- package/src/lib/useAutoRefresh.ts +31 -0
- package/src/lib/useCollapse.ts +31 -0
- package/src/lib/useFilteredData.ts +55 -0
- package/src/lib/yaml-loader-real.spec.ts +47 -0
- package/src/lib/yaml-loader.spec.ts +201 -0
- package/src/lib/yaml-loader.ts +265 -0
- package/src/routeTree.gen.ts +140 -0
- package/src/router.tsx +10 -0
- package/src/routes/__root.tsx +75 -0
- package/src/routes/api/watch.ts +29 -0
- package/src/routes/index.tsx +115 -0
- package/src/routes/milestones.tsx +50 -0
- package/src/routes/search.tsx +84 -0
- package/src/routes/tasks.tsx +63 -0
- package/src/services/progress-database.service.ts +46 -0
- package/src/styles.css +25 -0
- package/tsconfig.json +24 -0
- package/vite.config.ts +16 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# API Route Handlers Pattern
|
|
2
|
+
|
|
3
|
+
**Category**: Architecture
|
|
4
|
+
**Applicable To**: TanStack Start + Cloudflare Workers applications with REST API endpoints
|
|
5
|
+
**Status**: Stable
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
TanStack Start provides file-based API routing where each file in `src/routes/api/` becomes an HTTP endpoint. This pattern documents the standard approach for creating authenticated, type-safe API routes that delegate to service layers and return proper JSON responses with consistent error handling.
|
|
12
|
+
|
|
13
|
+
API routes use `createFileRoute` with the `server.handlers` configuration to define HTTP method handlers (GET, POST, PATCH, DELETE) that run exclusively on the server. Each handler follows a consistent structure: authenticate, validate, delegate to service, return response.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## When to Use This Pattern
|
|
18
|
+
|
|
19
|
+
✅ **Use this pattern when:**
|
|
20
|
+
- Building REST API endpoints consumed by client-side services
|
|
21
|
+
- Need server-side authentication enforcement
|
|
22
|
+
- Creating CRUD operations for domain entities
|
|
23
|
+
- Building endpoints consumed by external clients or webhooks
|
|
24
|
+
- Need consistent error handling and response formats
|
|
25
|
+
|
|
26
|
+
❌ **Don't use this pattern when:**
|
|
27
|
+
- Building real-time features (use Durable Objects WebSocket instead)
|
|
28
|
+
- Server functions (`createServerFn`) are sufficient for the use case
|
|
29
|
+
- The endpoint is purely for SSR data preloading (use `beforeLoad` instead)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Core Principles
|
|
34
|
+
|
|
35
|
+
1. **Auth First**: Every handler starts with authentication check
|
|
36
|
+
2. **Service Delegation**: Handlers delegate to database services, never access DB directly
|
|
37
|
+
3. **Consistent Responses**: All responses use `new Response(JSON.stringify(...))` with proper headers
|
|
38
|
+
4. **Error Boundaries**: Every handler wrapped in try/catch with consistent error response format
|
|
39
|
+
5. **File-Based Routing**: Route path derived from file location in `src/routes/api/`
|
|
40
|
+
6. **HTTP Method Handlers**: Use `server.handlers` with explicit GET/POST/PATCH/DELETE
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Implementation
|
|
45
|
+
|
|
46
|
+
### Structure
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
src/routes/api/
|
|
50
|
+
├── conversations/
|
|
51
|
+
│ ├── index.tsx # GET /api/conversations (list)
|
|
52
|
+
│ ├── create.tsx # POST /api/conversations/create
|
|
53
|
+
│ └── $id/
|
|
54
|
+
│ ├── index.tsx # GET/PATCH/DELETE /api/conversations/:id
|
|
55
|
+
│ └── messages.tsx # GET /api/conversations/:id/messages
|
|
56
|
+
├── groups/
|
|
57
|
+
│ ├── index.tsx # GET /api/groups
|
|
58
|
+
│ ├── create.tsx # POST /api/groups/create
|
|
59
|
+
│ └── $id/
|
|
60
|
+
│ ├── index.tsx # GET/PATCH/DELETE /api/groups/:id
|
|
61
|
+
│ └── members/
|
|
62
|
+
│ └── $userId.tsx # POST/DELETE /api/groups/:id/members/:userId
|
|
63
|
+
├── auth/
|
|
64
|
+
│ ├── session.tsx # POST /api/auth/session (create session)
|
|
65
|
+
│ └── logout.tsx # POST /api/auth/logout
|
|
66
|
+
└── storage/
|
|
67
|
+
└── upload.tsx # POST /api/storage/upload
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Code Example
|
|
71
|
+
|
|
72
|
+
#### Standard GET Handler (List)
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// src/routes/api/groups/index.tsx
|
|
76
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
77
|
+
import { getAuthSession } from '@/lib/auth/server-fn'
|
|
78
|
+
import { GroupConversationDatabaseService } from '@/services/group-conversation-database.service'
|
|
79
|
+
import { initFirebaseAdmin } from '@/lib/firebase-admin'
|
|
80
|
+
|
|
81
|
+
export const Route = createFileRoute('/api/groups/')({
|
|
82
|
+
server: {
|
|
83
|
+
handlers: {
|
|
84
|
+
GET: async () => {
|
|
85
|
+
initFirebaseAdmin()
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
// 1. Authenticate
|
|
89
|
+
const user = await getAuthSession()
|
|
90
|
+
if (!user) {
|
|
91
|
+
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
|
92
|
+
status: 401,
|
|
93
|
+
headers: { 'Content-Type': 'application/json' },
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 2. Delegate to service
|
|
98
|
+
const groups = await GroupConversationDatabaseService.listGroupConversations(user.uid)
|
|
99
|
+
|
|
100
|
+
// 3. Return success response
|
|
101
|
+
return new Response(JSON.stringify({ groups }), {
|
|
102
|
+
status: 200,
|
|
103
|
+
headers: { 'Content-Type': 'application/json' },
|
|
104
|
+
})
|
|
105
|
+
} catch (error) {
|
|
106
|
+
// 4. Handle errors consistently
|
|
107
|
+
console.error('[API] Error listing groups:', error)
|
|
108
|
+
return new Response(JSON.stringify({
|
|
109
|
+
error: 'Internal server error',
|
|
110
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
111
|
+
}), {
|
|
112
|
+
status: 500,
|
|
113
|
+
headers: { 'Content-Type': 'application/json' },
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Standard POST Handler (Create)
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// src/routes/api/groups/create.tsx
|
|
126
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
127
|
+
import { getAuthSession } from '@/lib/auth/server-fn'
|
|
128
|
+
import { GroupConversationDatabaseService } from '@/services/group-conversation-database.service'
|
|
129
|
+
import { CreateGroupSchema } from '@/schemas/group-conversation'
|
|
130
|
+
|
|
131
|
+
export const Route = createFileRoute('/api/groups/create')({
|
|
132
|
+
server: {
|
|
133
|
+
handlers: {
|
|
134
|
+
POST: async ({ request }) => {
|
|
135
|
+
initFirebaseAdmin()
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const user = await getAuthSession()
|
|
139
|
+
if (!user) {
|
|
140
|
+
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
|
141
|
+
status: 401,
|
|
142
|
+
headers: { 'Content-Type': 'application/json' },
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Parse and validate body
|
|
147
|
+
const body = await request.json()
|
|
148
|
+
const parsed = CreateGroupSchema.safeParse(body)
|
|
149
|
+
|
|
150
|
+
if (!parsed.success) {
|
|
151
|
+
return new Response(JSON.stringify({
|
|
152
|
+
error: 'Validation error',
|
|
153
|
+
details: parsed.error.issues,
|
|
154
|
+
}), {
|
|
155
|
+
status: 400,
|
|
156
|
+
headers: { 'Content-Type': 'application/json' },
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Delegate to service
|
|
161
|
+
const group = await GroupConversationDatabaseService.createGroupConversation(
|
|
162
|
+
user.uid,
|
|
163
|
+
parsed.data
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return new Response(JSON.stringify({ group }), {
|
|
167
|
+
status: 201,
|
|
168
|
+
headers: { 'Content-Type': 'application/json' },
|
|
169
|
+
})
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error('[API] Error creating group:', error)
|
|
172
|
+
return new Response(JSON.stringify({
|
|
173
|
+
error: 'Internal server error',
|
|
174
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
175
|
+
}), {
|
|
176
|
+
status: 500,
|
|
177
|
+
headers: { 'Content-Type': 'application/json' },
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
})
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### Dynamic Route Parameters
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
// src/routes/api/groups/$id/index.tsx
|
|
190
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
191
|
+
|
|
192
|
+
export const Route = createFileRoute('/api/groups/$id/')({
|
|
193
|
+
server: {
|
|
194
|
+
handlers: {
|
|
195
|
+
GET: async ({ params }) => {
|
|
196
|
+
const { id } = params // Group ID from URL
|
|
197
|
+
|
|
198
|
+
const user = await getAuthSession()
|
|
199
|
+
if (!user) {
|
|
200
|
+
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
|
201
|
+
status: 401,
|
|
202
|
+
headers: { 'Content-Type': 'application/json' },
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const group = await GroupConversationDatabaseService.getGroupConversation(user.uid, id)
|
|
207
|
+
|
|
208
|
+
if (!group) {
|
|
209
|
+
return new Response(JSON.stringify({ error: 'Not found' }), {
|
|
210
|
+
status: 404,
|
|
211
|
+
headers: { 'Content-Type': 'application/json' },
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return new Response(JSON.stringify({ group }), {
|
|
216
|
+
status: 200,
|
|
217
|
+
headers: { 'Content-Type': 'application/json' },
|
|
218
|
+
})
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
PATCH: async ({ params, request }) => {
|
|
222
|
+
const { id } = params
|
|
223
|
+
// ... update logic
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
DELETE: async ({ params }) => {
|
|
227
|
+
const { id } = params
|
|
228
|
+
// ... delete logic
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
})
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Response Format Convention
|
|
238
|
+
|
|
239
|
+
### Success Responses
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// List operations: wrap in plural key
|
|
243
|
+
return Response.json({ groups }) // 200
|
|
244
|
+
return Response.json({ conversations }) // 200
|
|
245
|
+
|
|
246
|
+
// Single entity operations
|
|
247
|
+
return Response.json({ group }) // 200 (get/update)
|
|
248
|
+
return Response.json({ group }) // 201 (create)
|
|
249
|
+
|
|
250
|
+
// Delete operations
|
|
251
|
+
return Response.json({ success: true }) // 200
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Error Responses
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
// Authentication
|
|
258
|
+
return Response.json({ error: 'Unauthorized' }, { status: 401 })
|
|
259
|
+
|
|
260
|
+
// Validation
|
|
261
|
+
return Response.json({
|
|
262
|
+
error: 'Validation error',
|
|
263
|
+
details: zodError.issues
|
|
264
|
+
}, { status: 400 })
|
|
265
|
+
|
|
266
|
+
// Not found
|
|
267
|
+
return Response.json({ error: 'Not found' }, { status: 404 })
|
|
268
|
+
|
|
269
|
+
// Forbidden
|
|
270
|
+
return Response.json({ error: 'Forbidden' }, { status: 403 })
|
|
271
|
+
|
|
272
|
+
// Rate limited
|
|
273
|
+
return Response.json({
|
|
274
|
+
error: 'Too many requests',
|
|
275
|
+
retryAfter: 60
|
|
276
|
+
}, { status: 429 })
|
|
277
|
+
|
|
278
|
+
// Server error
|
|
279
|
+
return Response.json({
|
|
280
|
+
error: 'Internal server error',
|
|
281
|
+
message: error.message
|
|
282
|
+
}, { status: 500 })
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Benefits
|
|
288
|
+
|
|
289
|
+
### 1. File-Based Discovery
|
|
290
|
+
API routes are discoverable by browsing `src/routes/api/`. The file path IS the URL path.
|
|
291
|
+
|
|
292
|
+
### 2. Consistent Auth Pattern
|
|
293
|
+
Every handler starts with the same auth check, making security audits straightforward.
|
|
294
|
+
|
|
295
|
+
### 3. Type-Safe Parameters
|
|
296
|
+
Dynamic route parameters (`$id`) are typed and extracted from `params`.
|
|
297
|
+
|
|
298
|
+
### 4. Server-Only Execution
|
|
299
|
+
Handlers in `server.handlers` never run on the client, preventing accidental secret exposure.
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## Trade-offs
|
|
304
|
+
|
|
305
|
+
### 1. Verbose Response Construction
|
|
306
|
+
**Downside**: `new Response(JSON.stringify(...))` is verbose compared to frameworks with built-in helpers.
|
|
307
|
+
**Mitigation**: Create helper functions like `jsonResponse(data, status)` if desired.
|
|
308
|
+
|
|
309
|
+
### 2. No Built-In Middleware
|
|
310
|
+
**Downside**: No middleware system for cross-cutting concerns (auth, logging, rate limiting).
|
|
311
|
+
**Mitigation**: Extract auth/validation into reusable functions called at the start of each handler.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Anti-Patterns
|
|
316
|
+
|
|
317
|
+
### ❌ Anti-Pattern 1: Direct Database Access in Route
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
// ❌ BAD: Direct Firestore call in route handler
|
|
321
|
+
GET: async () => {
|
|
322
|
+
const docs = await queryDocuments('groups', {}) // Direct DB access!
|
|
323
|
+
return Response.json({ groups: docs })
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ✅ GOOD: Delegate to service
|
|
327
|
+
GET: async () => {
|
|
328
|
+
const groups = await GroupDatabaseService.listGroups(user.uid)
|
|
329
|
+
return Response.json({ groups })
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### ❌ Anti-Pattern 2: Missing Error Handler
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
// ❌ BAD: No try/catch — unhandled errors crash the worker
|
|
337
|
+
GET: async () => {
|
|
338
|
+
const user = await getAuthSession()
|
|
339
|
+
const groups = await service.listGroups(user.uid) // Could throw!
|
|
340
|
+
return Response.json({ groups })
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ✅ GOOD: Wrapped in try/catch
|
|
344
|
+
GET: async () => {
|
|
345
|
+
try {
|
|
346
|
+
const user = await getAuthSession()
|
|
347
|
+
const groups = await service.listGroups(user.uid)
|
|
348
|
+
return Response.json({ groups })
|
|
349
|
+
} catch (error) {
|
|
350
|
+
return Response.json({ error: 'Internal server error' }, { status: 500 })
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### ❌ Anti-Pattern 3: Skipping Auth Check
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// ❌ BAD: No authentication — anyone can access
|
|
359
|
+
GET: async () => {
|
|
360
|
+
const groups = await service.listGroups('some-user-id')
|
|
361
|
+
return Response.json({ groups })
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ✅ GOOD: Always authenticate first
|
|
365
|
+
GET: async () => {
|
|
366
|
+
const user = await getAuthSession()
|
|
367
|
+
if (!user) return Response.json({ error: 'Unauthorized' }, { status: 401 })
|
|
368
|
+
const groups = await service.listGroups(user.uid)
|
|
369
|
+
return Response.json({ groups })
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## Related Patterns
|
|
376
|
+
|
|
377
|
+
- **[Library Services Pattern](./tanstack-cloudflare.library-services.md)**: API routes delegate to database services
|
|
378
|
+
- **[Auth Session Management](./tanstack-cloudflare.auth-session-management.md)**: `getAuthSession()` used in every handler
|
|
379
|
+
- **[Zod Schema Validation](./tanstack-cloudflare.zod-schema-validation.md)**: Request body validation with Zod
|
|
380
|
+
- **[Rate Limiting](./tanstack-cloudflare.rate-limiting.md)**: Rate limit API endpoints
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Checklist for Implementation
|
|
385
|
+
|
|
386
|
+
- [ ] Route file uses `createFileRoute` with `server.handlers`
|
|
387
|
+
- [ ] Every handler starts with `getAuthSession()` check
|
|
388
|
+
- [ ] Request body validated with Zod `safeParse` for POST/PATCH
|
|
389
|
+
- [ ] All responses include `Content-Type: application/json` header
|
|
390
|
+
- [ ] Error responses use consistent `{ error, message? }` format
|
|
391
|
+
- [ ] Every handler wrapped in try/catch
|
|
392
|
+
- [ ] Dynamic route parameters accessed via `params`
|
|
393
|
+
- [ ] Database operations delegated to service layer
|
|
394
|
+
- [ ] Firebase Admin SDK initialized at handler start
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
**Status**: Stable - Standard pattern for TanStack Start API endpoints
|
|
399
|
+
**Recommendation**: Use for all REST API endpoints in TanStack Start applications
|
|
400
|
+
**Last Updated**: 2026-02-28
|
|
401
|
+
**Contributors**: Patrick Michaelsen
|