kaddidlehopper 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.
Files changed (183) hide show
  1. package/CONTEXT.md +139 -0
  2. package/README.md +47 -0
  3. package/add-ons/ai/README.md +34 -0
  4. package/add-ons/ai/assets/_dot_env.local.append +13 -0
  5. package/add-ons/ai/assets/src/components/AIAssistant.tsx +149 -0
  6. package/add-ons/ai/assets/src/lib/ai-hook.ts +21 -0
  7. package/add-ons/ai/assets/src/lib/weather-tools.ts +30 -0
  8. package/add-ons/ai/assets/src/routes/api.chat.ts +94 -0
  9. package/add-ons/ai/assets/src/routes/chat.css +175 -0
  10. package/add-ons/ai/assets/src/routes/chat.tsx +141 -0
  11. package/add-ons/ai/info.json +27 -0
  12. package/add-ons/ai/package.json +17 -0
  13. package/add-ons/ai/small-logo.svg +8 -0
  14. package/dist/cli.js +251 -0
  15. package/dist/index.js +33 -0
  16. package/dist/types/cli.d.ts +8 -0
  17. package/dist/types/index.d.ts +2 -0
  18. package/dist/types/types.d.ts +14 -0
  19. package/dist/types.js +1 -0
  20. package/examples/blog/README.md +60 -0
  21. package/examples/blog/assets/content/posts/beach.md +12 -0
  22. package/examples/blog/assets/content/posts/jungle.md.ejs +12 -0
  23. package/examples/blog/assets/content/posts/mountains.md.ejs +12 -0
  24. package/examples/blog/assets/content/posts/snorkeling.md.ejs +12 -0
  25. package/examples/blog/assets/content/posts/waterfall.md.ejs +12 -0
  26. package/examples/blog/assets/content-collections.ts +30 -0
  27. package/examples/blog/assets/public/beach.jpg +0 -0
  28. package/examples/blog/assets/public/jungle.jpg +0 -0
  29. package/examples/blog/assets/public/mountains.jpg +0 -0
  30. package/examples/blog/assets/public/snorkeling.jpg +0 -0
  31. package/examples/blog/assets/public/waterfall.jpg +0 -0
  32. package/examples/blog/assets/src/components/Header.tsx +52 -0
  33. package/examples/blog/assets/src/components/VacayAssistant.tsx +205 -0
  34. package/examples/blog/assets/src/components/blog-posts.tsx +78 -0
  35. package/examples/blog/assets/src/components/ui/card.tsx +92 -0
  36. package/examples/blog/assets/src/lib/blog-ai-hook.ts +25 -0
  37. package/examples/blog/assets/src/lib/blog-tools.ts +111 -0
  38. package/examples/blog/assets/src/lib/utils.ts +6 -0
  39. package/examples/blog/assets/src/routes/__root.tsx +57 -0
  40. package/examples/blog/assets/src/routes/api.blog-chat.ts +117 -0
  41. package/examples/blog/assets/src/routes/category.$category.tsx +19 -0
  42. package/examples/blog/assets/src/routes/index.tsx +19 -0
  43. package/examples/blog/assets/src/routes/posts.$slug.tsx +63 -0
  44. package/examples/blog/assets/src/styles.css +138 -0
  45. package/examples/blog/info.json +43 -0
  46. package/examples/blog/package.json +23 -0
  47. package/examples/events/README.md +110 -0
  48. package/examples/events/assets/content/speakers/andre-costa.md +22 -0
  49. package/examples/events/assets/content/speakers/hans-mueller.md.ejs +22 -0
  50. package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +22 -0
  51. package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +22 -0
  52. package/examples/events/assets/content/speakers/marie-dubois.md.ejs +20 -0
  53. package/examples/events/assets/content/speakers/priya-sharma.md.ejs +22 -0
  54. package/examples/events/assets/content/talks/croissant-lamination-secrets.md +39 -0
  55. package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +39 -0
  56. package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +39 -0
  57. package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +39 -0
  58. package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +36 -0
  59. package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +32 -0
  60. package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +39 -0
  61. package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +39 -0
  62. package/examples/events/assets/content-collections.ts +56 -0
  63. package/examples/events/assets/public/background-1.jpg +0 -0
  64. package/examples/events/assets/public/background-2.jpg +0 -0
  65. package/examples/events/assets/public/background-3.jpg +0 -0
  66. package/examples/events/assets/public/background-4.jpg +0 -0
  67. package/examples/events/assets/public/conference-logo.png +0 -0
  68. package/examples/events/assets/public/favicon.ico +0 -0
  69. package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
  70. package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
  71. package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
  72. package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
  73. package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
  74. package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
  75. package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
  76. package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
  77. package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
  78. package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
  79. package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
  80. package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
  81. package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
  82. package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
  83. package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
  84. package/examples/events/assets/public/tanstack-word-logo-white.svg +1 -0
  85. package/examples/events/assets/src/components/Header.tsx +59 -0
  86. package/examples/events/assets/src/components/HeaderNav.tsx +67 -0
  87. package/examples/events/assets/src/components/HeroCarousel.tsx +61 -0
  88. package/examples/events/assets/src/components/RemyAssistant.tsx +207 -0
  89. package/examples/events/assets/src/components/SpeakerCard.tsx +67 -0
  90. package/examples/events/assets/src/components/TalkCard.tsx +77 -0
  91. package/examples/events/assets/src/components/ui/card.tsx +92 -0
  92. package/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
  93. package/examples/events/assets/src/lib/conference-tools.ts +210 -0
  94. package/examples/events/assets/src/lib/model-selection.ts +1 -0
  95. package/examples/events/assets/src/lib/utils.ts +6 -0
  96. package/examples/events/assets/src/routes/__root.tsx +70 -0
  97. package/examples/events/assets/src/routes/api.remy-chat.ts +119 -0
  98. package/examples/events/assets/src/routes/index.tsx +192 -0
  99. package/examples/events/assets/src/routes/schedule.index.tsx +274 -0
  100. package/examples/events/assets/src/routes/speakers.$slug.tsx +122 -0
  101. package/examples/events/assets/src/routes/speakers.index.tsx +40 -0
  102. package/examples/events/assets/src/routes/talks.$slug.tsx +116 -0
  103. package/examples/events/assets/src/routes/talks.index.tsx +40 -0
  104. package/examples/events/assets/src/styles.css +182 -0
  105. package/examples/events/info.json +74 -0
  106. package/examples/events/package.json +23 -0
  107. package/examples/marketing/README.md +60 -0
  108. package/examples/marketing/assets/public/logo.png +0 -0
  109. package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
  110. package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
  111. package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
  112. package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
  113. package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
  114. package/examples/marketing/assets/src/components/Header.tsx +36 -0
  115. package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +162 -0
  116. package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +53 -0
  117. package/examples/marketing/assets/src/data/motorcycles.ts.ejs +77 -0
  118. package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +24 -0
  119. package/examples/marketing/assets/src/lib/motorcycle-tools.ts +42 -0
  120. package/examples/marketing/assets/src/routes/__root.tsx +57 -0
  121. package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +78 -0
  122. package/examples/marketing/assets/src/routes/index.tsx +72 -0
  123. package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +56 -0
  124. package/examples/marketing/assets/src/store/motorcycle-assistant.ts +3 -0
  125. package/examples/marketing/assets/src/styles.css +212 -0
  126. package/examples/marketing/info.json +38 -0
  127. package/examples/marketing/package.json +14 -0
  128. package/examples/resume/README.md +82 -0
  129. package/examples/resume/assets/content/education/code-school.md +17 -0
  130. package/examples/resume/assets/content/jobs/freelance.md.ejs +13 -0
  131. package/examples/resume/assets/content/jobs/initech-junior.md +20 -0
  132. package/examples/resume/assets/content/jobs/initech-lead.md.ejs +29 -0
  133. package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +28 -0
  134. package/examples/resume/assets/content-collections.ts +36 -0
  135. package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
  136. package/examples/resume/assets/src/components/Header.tsx +33 -0
  137. package/examples/resume/assets/src/components/ResumeAssistant.tsx +193 -0
  138. package/examples/resume/assets/src/components/ui/badge.tsx +46 -0
  139. package/examples/resume/assets/src/components/ui/card.tsx +92 -0
  140. package/examples/resume/assets/src/components/ui/checkbox.tsx +30 -0
  141. package/examples/resume/assets/src/components/ui/hover-card.tsx +44 -0
  142. package/examples/resume/assets/src/components/ui/separator.tsx +26 -0
  143. package/examples/resume/assets/src/lib/resume-ai-hook.ts +21 -0
  144. package/examples/resume/assets/src/lib/resume-tools.ts +165 -0
  145. package/examples/resume/assets/src/lib/utils.ts +6 -0
  146. package/examples/resume/assets/src/routes/api.resume-chat.ts +110 -0
  147. package/examples/resume/assets/src/routes/index.tsx +220 -0
  148. package/examples/resume/assets/src/styles.css +138 -0
  149. package/examples/resume/info.json +25 -0
  150. package/examples/resume/package.json +26 -0
  151. package/package.json +39 -0
  152. package/project/base/_dot_claude/skills/content-collections/SKILL.md +505 -0
  153. package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +410 -0
  154. package/project/base/_dot_claude/skills/netlify-db/SKILL.md +424 -0
  155. package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +419 -0
  156. package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +243 -0
  157. package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +372 -0
  158. package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +421 -0
  159. package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +426 -0
  160. package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +493 -0
  161. package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +430 -0
  162. package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +445 -0
  163. package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +494 -0
  164. package/project/base/_dot_gitignore +8 -0
  165. package/project/base/netlify.toml +7 -0
  166. package/project/base/package.json +33 -0
  167. package/project/base/public/favicon.ico +0 -0
  168. package/project/base/public/tanstack-circle-logo.png +0 -0
  169. package/project/base/public/tanstack-word-logo-white.svg +1 -0
  170. package/project/base/src/components/Header.tsx +17 -0
  171. package/project/base/src/components/HeaderNav.tsx.ejs +179 -0
  172. package/project/base/src/router.tsx +15 -0
  173. package/project/base/src/routes/__root.tsx +57 -0
  174. package/project/base/src/routes/index.tsx +48 -0
  175. package/project/base/src/styles.css +15 -0
  176. package/project/base/tsconfig.json +28 -0
  177. package/project/base/vite.config.ts.ejs +25 -0
  178. package/project/packages.json +22 -0
  179. package/scripts/check-outdated-packages.js +421 -0
  180. package/src/cli.ts +343 -0
  181. package/src/index.ts +49 -0
  182. package/src/types.ts +15 -0
  183. package/tsconfig.json +17 -0
@@ -0,0 +1,421 @@
1
+ ---
2
+ name: tanstack-start-api-routes
3
+ description: Create API routes (server routes) in TanStack Start for handling HTTP requests. Use when building REST APIs, webhooks, or any HTTP endpoint that returns data rather than rendering a page.
4
+ license: Apache-2.0
5
+ metadata:
6
+ author: tanstack
7
+ version: "1.0"
8
+ ---
9
+
10
+ # TanStack Start API Routes (Server Routes)
11
+
12
+ TanStack Start allows you to create API endpoints using the `server` property on routes. These run server-side and handle raw HTTP requests.
13
+
14
+ ## When to Use
15
+
16
+ - Building REST API endpoints
17
+ - Handling webhooks
18
+ - File uploads/downloads
19
+ - Any endpoint that returns data (not HTML)
20
+
21
+ **Note**: For RPC-style server logic callable from components, use **server functions** instead. Server routes are for traditional HTTP endpoints.
22
+
23
+ ## Basic Server Route
24
+
25
+ ```typescript
26
+ // src/routes/api/hello.ts
27
+ import { createFileRoute } from '@tanstack/react-router';
28
+
29
+ export const Route = createFileRoute('/api/hello')({
30
+ server: {
31
+ handlers: {
32
+ GET: async ({ request }) => {
33
+ return new Response(JSON.stringify({ message: 'Hello, World!' }), {
34
+ headers: { 'Content-Type': 'application/json' },
35
+ });
36
+ },
37
+ },
38
+ },
39
+ });
40
+ ```
41
+
42
+ ## Multiple HTTP Methods
43
+
44
+ ```typescript
45
+ // src/routes/api/users.ts
46
+ import { createFileRoute } from '@tanstack/react-router';
47
+
48
+ export const Route = createFileRoute('/api/users')({
49
+ server: {
50
+ handlers: {
51
+ GET: async ({ request }) => {
52
+ const users = await fetchUsers();
53
+ return Response.json(users);
54
+ },
55
+
56
+ POST: async ({ request }) => {
57
+ const body = await request.json();
58
+ const newUser = await createUser(body);
59
+ return Response.json(newUser, { status: 201 });
60
+ },
61
+ },
62
+ },
63
+ });
64
+ ```
65
+
66
+ ## Dynamic Parameters
67
+
68
+ ```typescript
69
+ // src/routes/api/users/$id.ts
70
+ import { createFileRoute } from '@tanstack/react-router';
71
+
72
+ export const Route = createFileRoute('/api/users/$id')({
73
+ server: {
74
+ handlers: {
75
+ GET: async ({ params }) => {
76
+ const { id } = params;
77
+ const user = await getUser(id);
78
+
79
+ if (!user) {
80
+ return new Response('User not found', { status: 404 });
81
+ }
82
+
83
+ return Response.json(user);
84
+ },
85
+
86
+ PUT: async ({ request, params }) => {
87
+ const { id } = params;
88
+ const body = await request.json();
89
+ const updatedUser = await updateUser(id, body);
90
+ return Response.json(updatedUser);
91
+ },
92
+
93
+ DELETE: async ({ params }) => {
94
+ const { id } = params;
95
+ await deleteUser(id);
96
+ return new Response(null, { status: 204 });
97
+ },
98
+ },
99
+ },
100
+ });
101
+ ```
102
+
103
+ ## Multiple Dynamic Parameters
104
+
105
+ ```typescript
106
+ // src/routes/api/users/$userId/posts/$postId.ts
107
+ import { createFileRoute } from '@tanstack/react-router';
108
+
109
+ export const Route = createFileRoute('/api/users/$userId/posts/$postId')({
110
+ server: {
111
+ handlers: {
112
+ GET: async ({ params }) => {
113
+ const { userId, postId } = params;
114
+ const post = await getUserPost(userId, postId);
115
+ return Response.json(post);
116
+ },
117
+ },
118
+ },
119
+ });
120
+ ```
121
+
122
+ ## Handler Context
123
+
124
+ The handler receives a context object with:
125
+
126
+ ```typescript
127
+ export const Route = createFileRoute('/api/example')({
128
+ server: {
129
+ handlers: {
130
+ GET: async (context) => {
131
+ // Request object (Web API Request)
132
+ const { request } = context;
133
+
134
+ // URL parameters from route
135
+ const { params } = context;
136
+
137
+ // Get headers
138
+ const authHeader = request.headers.get('Authorization');
139
+
140
+ // Get query parameters
141
+ const url = new URL(request.url);
142
+ const searchParams = url.searchParams;
143
+ const page = searchParams.get('page');
144
+
145
+ return Response.json({ page });
146
+ },
147
+ },
148
+ },
149
+ });
150
+ ```
151
+
152
+ ## Request Body Handling
153
+
154
+ ### JSON Body
155
+
156
+ ```typescript
157
+ POST: async ({ request }) => {
158
+ const body = await request.json();
159
+ // body is parsed JSON
160
+ return Response.json({ received: body });
161
+ },
162
+ ```
163
+
164
+ ### Form Data
165
+
166
+ ```typescript
167
+ POST: async ({ request }) => {
168
+ const formData = await request.formData();
169
+ const name = formData.get('name');
170
+ const email = formData.get('email');
171
+
172
+ return Response.json({ name, email });
173
+ },
174
+ ```
175
+
176
+ ### Raw Text/Binary
177
+
178
+ ```typescript
179
+ POST: async ({ request }) => {
180
+ const text = await request.text();
181
+ // or
182
+ const buffer = await request.arrayBuffer();
183
+
184
+ return new Response('Received', { status: 200 });
185
+ },
186
+ ```
187
+
188
+ ## Response Helpers
189
+
190
+ ```typescript
191
+ // JSON response
192
+ return Response.json({ data: 'value' });
193
+
194
+ // JSON with status
195
+ return Response.json({ error: 'Not found' }, { status: 404 });
196
+
197
+ // Plain text
198
+ return new Response('Hello', {
199
+ headers: { 'Content-Type': 'text/plain' },
200
+ });
201
+
202
+ // HTML
203
+ return new Response('<h1>Hello</h1>', {
204
+ headers: { 'Content-Type': 'text/html' },
205
+ });
206
+
207
+ // Redirect
208
+ return Response.redirect('https://example.com', 302);
209
+
210
+ // No content
211
+ return new Response(null, { status: 204 });
212
+
213
+ // Stream
214
+ const stream = new ReadableStream({ ... });
215
+ return new Response(stream, {
216
+ headers: { 'Content-Type': 'application/octet-stream' },
217
+ });
218
+ ```
219
+
220
+ ## Error Handling
221
+
222
+ ```typescript
223
+ export const Route = createFileRoute('/api/users/$id')({
224
+ server: {
225
+ handlers: {
226
+ GET: async ({ params }) => {
227
+ try {
228
+ const user = await getUser(params.id);
229
+
230
+ if (!user) {
231
+ return Response.json(
232
+ { error: 'User not found' },
233
+ { status: 404 }
234
+ );
235
+ }
236
+
237
+ return Response.json(user);
238
+ } catch (error) {
239
+ console.error('Error fetching user:', error);
240
+
241
+ return Response.json(
242
+ { error: 'Internal server error' },
243
+ { status: 500 }
244
+ );
245
+ }
246
+ },
247
+ },
248
+ },
249
+ });
250
+ ```
251
+
252
+ ## Authentication
253
+
254
+ ```typescript
255
+ // src/routes/api/protected.ts
256
+ import { createFileRoute } from '@tanstack/react-router';
257
+ import { verifyToken } from '../lib/auth';
258
+
259
+ export const Route = createFileRoute('/api/protected')({
260
+ server: {
261
+ handlers: {
262
+ GET: async ({ request }) => {
263
+ const authHeader = request.headers.get('Authorization');
264
+
265
+ if (!authHeader?.startsWith('Bearer ')) {
266
+ return Response.json(
267
+ { error: 'Missing authorization header' },
268
+ { status: 401 }
269
+ );
270
+ }
271
+
272
+ const token = authHeader.slice(7);
273
+ const user = await verifyToken(token);
274
+
275
+ if (!user) {
276
+ return Response.json(
277
+ { error: 'Invalid token' },
278
+ { status: 401 }
279
+ );
280
+ }
281
+
282
+ // Proceed with authenticated request
283
+ return Response.json({ user, message: 'Protected data' });
284
+ },
285
+ },
286
+ },
287
+ });
288
+ ```
289
+
290
+ ## CORS Headers
291
+
292
+ ```typescript
293
+ const corsHeaders = {
294
+ 'Access-Control-Allow-Origin': '*',
295
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
296
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
297
+ };
298
+
299
+ export const Route = createFileRoute('/api/data')({
300
+ server: {
301
+ handlers: {
302
+ // Handle preflight
303
+ OPTIONS: async () => {
304
+ return new Response(null, {
305
+ status: 204,
306
+ headers: corsHeaders,
307
+ });
308
+ },
309
+
310
+ GET: async ({ request }) => {
311
+ const data = await fetchData();
312
+
313
+ return Response.json(data, {
314
+ headers: corsHeaders,
315
+ });
316
+ },
317
+ },
318
+ },
319
+ });
320
+ ```
321
+
322
+ ## File Naming Conventions
323
+
324
+ | File Path | API Route |
325
+ |-----------|-----------|
326
+ | `routes/api/hello.ts` | `GET /api/hello` |
327
+ | `routes/api/users.ts` | `GET /api/users` |
328
+ | `routes/api/users/$id.ts` | `GET /api/users/:id` |
329
+ | `routes/api/users.index.ts` | `GET /api/users` |
330
+ | `routes/api/file/$.ts` | `GET /api/file/*` (catch-all) |
331
+
332
+ ## Combined Route + API
333
+
334
+ A single file can handle both page rendering and API:
335
+
336
+ ```typescript
337
+ // src/routes/posts.$postId.tsx
338
+ import { createFileRoute } from '@tanstack/react-router';
339
+
340
+ export const Route = createFileRoute('/posts/$postId')({
341
+ // Server route handlers (API)
342
+ server: {
343
+ handlers: {
344
+ // GET /posts/123 with Accept: application/json → JSON response
345
+ GET: async ({ request, params }) => {
346
+ const accept = request.headers.get('Accept');
347
+
348
+ if (accept?.includes('application/json')) {
349
+ const post = await getPost(params.postId);
350
+ return Response.json(post);
351
+ }
352
+
353
+ // Fall through to page rendering
354
+ return undefined;
355
+ },
356
+ },
357
+ },
358
+
359
+ // Page loader and component
360
+ loader: async ({ params }) => {
361
+ const post = await getPost(params.postId);
362
+ return { post };
363
+ },
364
+
365
+ component: PostComponent,
366
+ });
367
+ ```
368
+
369
+ ## Webhooks Example
370
+
371
+ ```typescript
372
+ // src/routes/api/webhooks/stripe.ts
373
+ import { createFileRoute } from '@tanstack/react-router';
374
+ import Stripe from 'stripe';
375
+
376
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
377
+
378
+ export const Route = createFileRoute('/api/webhooks/stripe')({
379
+ server: {
380
+ handlers: {
381
+ POST: async ({ request }) => {
382
+ const sig = request.headers.get('stripe-signature');
383
+ const body = await request.text();
384
+
385
+ let event: Stripe.Event;
386
+
387
+ try {
388
+ event = stripe.webhooks.constructEvent(
389
+ body,
390
+ sig!,
391
+ process.env.STRIPE_WEBHOOK_SECRET!
392
+ );
393
+ } catch (err) {
394
+ return Response.json(
395
+ { error: 'Invalid signature' },
396
+ { status: 400 }
397
+ );
398
+ }
399
+
400
+ switch (event.type) {
401
+ case 'checkout.session.completed':
402
+ await handleCheckoutComplete(event.data.object);
403
+ break;
404
+ // Handle other events...
405
+ }
406
+
407
+ return Response.json({ received: true });
408
+ },
409
+ },
410
+ },
411
+ });
412
+ ```
413
+
414
+ ## Best Practices
415
+
416
+ 1. **Use server functions for RPC** - If calling from components, prefer `createServerFn`
417
+ 2. **Validate input** - Always validate request bodies and parameters
418
+ 3. **Handle errors** - Return appropriate status codes and messages
419
+ 4. **Set correct headers** - Content-Type, CORS, caching as needed
420
+ 5. **Keep handlers focused** - One responsibility per endpoint
421
+ 6. **Use TypeScript** - Type your request/response bodies