Special Sponsor:PromptBuilder— Fast, consistent prompt creation powered by 1,000+ expert templates.
Make your Product visible here.Contact Us
Taking new clients

Need a dev team that ships?

011BQ builds TypeScript-first products, migrations, and internal tools for startups and scale-ups.

  • JS/TS migration & codebase modernisation
  • Custom dev tools & internal platforms
  • React, Next.js & Node.js engineering
  • Code review, architecture & tech advisory

Or reach us directly

011bq.com

Send us a message

We respond within 1 business day.

check_dark

Thank You!

Your message has been successfully sent. We will get back to you soon!

Message sent!

Thanks for reaching out. The 011BQ team will get back to you within 1 business day.

HomeChevronBlogChevronPrisma with TypeScript and Zod: The Complete Validation Guide

Prisma with TypeScript and Zod: The Complete Validation Guide

j
js2ts Team
22/05/2026·2 minutes 0 seconds read
Prisma with TypeScript and Zod: The Complete Validation GuidePrisma with TypeScript and Zod: The Complete Validation Guide

Why Prisma Needs Zod

Prisma gives you TypeScript types for your database models — but those types only exist at compile time. When an API request arrives, it's untyped data from the outside world. Zod bridges this gap: it validates the incoming data at runtime so that only valid, correctly-typed data reaches your Prisma queries.

The combination of Prisma (database layer) + Zod (validation layer) + TypeScript (type safety) is the standard stack for modern TypeScript backends.

From Prisma Model to Zod Schema

Given a Prisma model, here's how to create the corresponding Zod schema:

// Prisma schema (schema.prisma)
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  role      Role     @default(USER)
  createdAt DateTime @default(now())
  posts     Post[]
}

enum Role {
  ADMIN
  USER
}
// Zod schema (user.schema.ts)
import { z } from 'zod';

export const RoleSchema = z.enum(['ADMIN', 'USER']);

export const UserSchema = z.object({
  id: z.number().int(),
  name: z.string().min(1, 'Name is required'),
  email: z.string().email('Invalid email format'),
  role: RoleSchema,
  createdAt: z.date(),
});

// For create operations (no id, no createdAt)
export const CreateUserSchema = z.object({
  name: z.string().min(1, 'Name is required'),
  email: z.string().email('Invalid email format'),
  role: RoleSchema.optional().default('USER'),
});

// For update operations (all fields optional)
export const UpdateUserSchema = CreateUserSchema.partial();

export type User = z.infer<typeof UserSchema>;
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
export type UpdateUserInput = z.infer<typeof UpdateUserSchema>;

Use our Prisma to Zod converter to automatically generate the base schemas from your Prisma models.

API Route Validation Pattern

Here's a complete Next.js API route using Prisma + Zod:

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { CreateUserSchema } from '@/schemas/user.schema';

export async function POST(request: NextRequest) {
  const body: unknown = await request.json();

  const result = CreateUserSchema.safeParse(body);
  if (!result.success) {
    return NextResponse.json(
      { errors: result.error.flatten().fieldErrors },
      { status: 400 }
    );
  }

  const user = await prisma.user.create({
    data: result.data, // fully typed, matches Prisma's create input
  });

  return NextResponse.json(user, { status: 201 });
}

tRPC + Prisma + Zod

tRPC makes the Prisma + Zod combination even more powerful. Zod schemas become the input validators for your tRPC procedures:

import { z } from 'zod';
import { router, publicProcedure } from '../trpc';
import { CreateUserSchema } from '@/schemas/user.schema';
import { prisma } from '@/lib/prisma';

export const userRouter = router({
  create: publicProcedure
    .input(CreateUserSchema)
    .mutation(async ({ input }) => {
      // input is fully typed as CreateUserInput
      return prisma.user.create({ data: input });
    }),

  getById: publicProcedure
    .input(z.object({ id: z.number().int() }))
    .query(async ({ input }) => {
      return prisma.user.findUnique({ where: { id: input.id } });
    }),
});

Using prisma-zod-generator

For larger projects, consider the prisma-zod-generator package which auto-generates Zod schemas every time you run prisma generate:

generator zod {
  provider = "prisma-zod-generator"
  output   = "./generated/zod"
}

This is the production approach — schemas always stay in sync with your Prisma models without manual maintenance.

Share