Type-Safe Validation with Drizzle and oRPC
One definition, validated at every boundary
Most TypeScript backends end up with validation defined in multiple places. You write a database schema, then write Zod schemas for your API, then maybe write more schemas for your client SDK. They drift. Fields get added to the database but not the API validation. Or the API accepts fields the database doesn't have. The types say everything is fine, but runtime tells a different story.
The pattern I want to walk through uses Drizzle as the source of truth for your data model, generates Zod schemas from that, and flows those schemas into oRPC contracts. One definition, validated at every boundary.
The Tools
Drizzle is a TypeScript ORM that lets you define your database schema in code. It can generate Zod schemas directly from your table definitions, which means your validation stays tied to your actual data model.
oRPC is a type-safe RPC framework. You define contracts with input and output schemas, and it handles the plumbing between client and server. The contracts use Zod, so anything Drizzle generates can flow directly into your API definition.
The Base Pattern
Start with a Drizzle schema, generate Zod schemas from it, and use those in oRPC contracts. The contract is now tied to the database. If you add a column to the users table, the Zod schemas update, and the oRPC contracts update. TypeScript will tell you everywhere the change matters.
Schema Derivation
Raw schemas are too simple for real applications. You need derived schemas: versions of the base that omit, require, or transform fields for specific contexts. Zod makes this straightforward with .omit(), .pick(), .partial(), and .extend().
The Full Picture
The data flow: Drizzle schema → drizzle-zod generates base Zod schemas → Refinements add business validation → Derivation helpers create context-specific variants → oRPC contracts consume the appropriate variant → oRPC client gets full type inference.
Changes propagate automatically. Add a column to the Drizzle schema, and TypeScript tells you everywhere you need to handle it.