---
description: API Contracts
alwaysApply: false
---

# API Contracts

Defining and maintaining contracts between frontend and backend.

## Why Contracts Matter

- **Type safety** — catch mismatches at compile time
- **Single source of truth** — one schema drives both sides
- **Parallel development** — frontend and backend work independently

## Schema-Driven Development

Define Zod schemas first, derive types:

```ts
import { z } from 'zod';
export const CreateUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
});
export const UserResponseSchema = z.object({
  id: z.string(), email: z.string(), name: z.string(),
  createdAt: z.string().datetime(),
});
export type CreateUserRequest = z.infer<typeof CreateUserSchema>;
```

### Backend — validate input

```ts
app.post('/users', async (req, res) => {
  const parsed = CreateUserSchema.safeParse(req.body);
  if (!parsed.success) return res.status(422).json({ error: parsed.error });
  res.status(201).json({ data: await createUser(parsed.data) });
});
```

### Frontend — validate before sending

```ts
async function createUser(data: CreateUserRequest) {
  const res = await fetch('/api/users', {
    method: 'POST', body: JSON.stringify(CreateUserSchema.parse(data)),
    headers: { 'Content-Type': 'application/json' },
  });
  if (!res.ok) throw new ApiError(res);
  return (await res.json()).data;
}
```

## Type-Safe Clients

Use **tRPC** for end-to-end type safety, or generate OpenAPI specs from Zod via `zod-to-openapi`.

## Contract Testing

```ts
it('POST /users returns valid UserResponse', async () => {
  const res = await request(app)
    .post('/users').send({ email: 'test@example.com', name: 'Test' });
  expect(res.status).toBe(201);
  expect(UserResponseSchema.safeParse(res.body.data).success).toBe(true);
});
```

## Versioning

URL versioning (`/api/v1/users`) for breaking changes. Add new fields as optional for backwards compatibility.

## Anti-Patterns

- Defining request/response shapes independently on frontend and backend
- Skipping server-side validation because the client validates
- Evolving APIs without contract tests to catch breakage
