Sharing Types Between Frontend & Backend
Part of the Full Stack Developer Roadmap
In short
If the API and the React app each keep their own User type, one day only one of them changes. Put it in one shared place and both sides break together, loudly, at build time.
Defining your data models once (e.g. in a shared package or monorepo) so frontend and backend can't silently drift apart on what a `User` object actually looks like.

Think of it like this
One guest list for both families
At a wedding, if the bride's side and the groom's side each keep a separate guest list, someone gets invited twice and someone is forgotten. Smart families keep one shared list both sides read from. Shared types are that single list.
Watch it happen
A monorepo with a shared package
1/5One repo holds the frontend, the backend and a shared package, linked with npm or pnpm workspaces.
The big ideas
What you need to know
A shared workspace package
Make packages/shared with its own package.json name like @myapp/shared, and depend on it from both apps.
Share schemas, not just types
Export a Zod schema and infer the type from it. The API validates input with it, the frontend validates responses.
Never share server secrets
The shared package goes into the browser bundle. Keep database code and secrets in the API app only.
import type for type-only
import type { User } is erased at build time, so no runtime code gets pulled in by accident.
See it in code
Walk through the code
1// packages/shared/src/user.ts2import { z } from "zod";3export const UserSchema = z.object({ id: z.string(), name: z.string(), email: z.email() });4export type User = z.infer<typeof UserSchema>;5 6// apps/api/src/users.route.ts7import type { User } from "@myapp/shared";8app.get("/api/users/:id", async (req, res) => {9 const user: User = await getUser(req.params.id);10 res.json(user);11});12 13// apps/web/src/api.ts14import { UserSchema, type User } from "@myapp/shared";15export const fetchUser = async (id: string): Promise<User> =>16 UserSchema.parse(await (await fetch(`/api/users/${id}`)).json());1/3The schema lives in the shared package. The User type is inferred from it.
Three files, one definition of User. Uses Zod 4, where z.email() is a top-level helper.
Save yourself the pain
Common mistakes
Oops
Copy-pasting types.ts into both the frontend and backend repos.
Do this instead
Use one shared package in a monorepo, or publish it as a private package both apps install.
Oops
Putting the Prisma client or database helpers in the shared package.
Do this instead
Share only types, schemas and pure helpers. Anything server-only must stay in the API app.
Your mission
Set up a tiny monorepo
Quick check
Why export a Zod schema from the shared package instead of only a TypeScript type?
Go deeper
- 1Set up a shared `types` folder or package used by both your frontend and backend.
- 2Change a shared type and confirm both codebases immediately show the resulting type errors.
- 3Learn how monorepo tools (Turborepo, Nx) make this pattern practical at scale.
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.