An API's contract usually lives in two places: the backend and the frontend. Any project where you try to keep those two in sync by hand will eventually hit a response shape mismatch in the wild.
Even on projects where I didn't keep the Node.js/.NET backend and Next.js frontend in the same monorepo, I still extracted at least the response types into a separate 'types' package that both sides import from the same source. That simple discipline nearly eliminated the 'backend renamed a field but the frontend still expects the old name' class of bugs.
Schema validation libraries like Zod are the second critical layer here. Defining the schema once and deriving the TypeScript type from it (z.infer) feeds both runtime validation and compile-time type safety from the same source. Passing the incoming request body through the schema, instead of casting it directly to a type, prevents silently accepting malformed data in production.
I type error responses with the same discipline: using separate, discriminated union types for success and failure states removes the ambiguous 'data might exist or might not' checks on the frontend.
Type safety isn't something you set up once and forget — it's a habit you repeat with every endpoint you add.
{ }
