Shared Package
Package: @my-app/shared
Path: packages/shared/
The shared package is the single source of truth for data types and validation schemas that are used by both the frontend and backend. It prevents type drift between the two apps.
Purpose
- Define Zod schemas as the authoritative data contracts
- Infer TypeScript types from those schemas (no manual type duplication)
- Export everything from a single entry point (
index.ts) - Build to both ESM and CJS for compatibility with Vue (ESM) and Node.js (CJS)
Package Exports
json
{
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
}
}Build with:
bash
pnpm --filter @my-app/shared build
# runs: tsc --project tsconfig.jsonCross-App Import Rules
✅ apps/frontend → @my-app/shared
✅ apps/backend → @my-app/shared
❌ packages/shared → apps/frontend (FORBIDDEN)
❌ packages/shared → apps/backend (FORBIDDEN)Adding a New Schema
- Create
packages/shared/src/schemas/<name>.schema.ts:
ts
import { z } from 'zod'
export const DeviceSchema = z.object({
_id: z.string(),
code: z.string(),
name: z.string().optional(),
unit_id: z.string().optional(),
})
export type Device = z.infer<typeof DeviceSchema>- Re-export from
packages/shared/src/index.ts:
ts
export { DeviceSchema, type Device } from './schemas/device.schema'- Re-export from
packages/shared/src/types/index.tsif needed:
ts
export type { Device } from '../schemas/device.schema'- Import in app code:
ts
// In frontend (Vue)
import type { Device } from '@my-app/shared'
// In backend (Node.js)
import { DeviceSchema, type Device } from '@my-app/shared'
const parsed = DeviceSchema.parse(req.body)- Rebuild shared package:
bash
pnpm --filter @my-app/shared buildDependency
zod@^3.22.0Zod is the only runtime dependency. It provides both schema validation and TypeScript type inference, eliminating the need for separate validation and type-definition libraries.
