Add an API Endpoint
1. Define Zod schemas in shared
Section titled “1. Define Zod schemas in shared”Add request/response schemas to shared/src/types/api.ts:
import { z } from 'zod';
export const CreateWidgetRequest = z.object({ name: z.string().min(1),});export type CreateWidgetRequest = z.infer<typeof CreateWidgetRequest>;
export const CreateWidgetResponse = z.object({ widget: z.object({ id: z.string().uuid(), name: z.string(), created_at: z.string(), }),});export type CreateWidgetResponse = z.infer<typeof CreateWidgetResponse>;2. Add the route enum
Section titled “2. Add the route enum”Add the route to shared/src/routes.ts:
export enum ApiRoutes { // ...existing routes Widgets = '/api/widgets', WidgetById = '/api/widgets/:id',}3. Write the handler
Section titled “3. Write the handler”Create server/src/routes/widgets.ts. Validate the request body with the Zod schema:
import type { FastifyInstance } from 'fastify';import { ApiRoutes, CreateWidgetRequest } from '@codecosts/shared';import type { CreateWidgetResponse } from '@codecosts/shared';import logger from '../logger';
export default async function widgetRoutes(app: FastifyInstance) { app.post(ApiRoutes.Widgets, async (req, reply) => { const parsed = CreateWidgetRequest.safeParse(req.body); if (!parsed.success) { reply.code(400).send({ error: 'Invalid request', details: parsed.error.message }); return; }
const { name } = parsed.data; // ... implementation logger.info({ component: 'widgets', action: 'created', userId: req.user?.id }, 'Widget created'); return { widget } satisfies CreateWidgetResponse; });}4. Register in server.ts
Section titled “4. Register in server.ts”Inside the protected routes plugin:
app.register(async (protectedRoutes) => { protectedRoutes.addHook('onRequest', authHook); protectedRoutes.register(widgetRoutes);});5. Write tests
Section titled “5. Write tests”Add tests in server/src/__tests__/widgets.test.ts covering:
- Happy path (201 with correct body)
- Auth required (401 without token)
- Validation (400 with bad input — Zod rejects it)
- Not found (404 for missing resources)
6. Verify
Section titled “6. Verify”bun run typecheckbun run lintbun test 2>&1 | tee /tmp/test.txt