This episode brings together models, schemas, and validation: designing a database schema with Prisma, validating request input with Zod, and applying validation in Express routes before data touches the database.

The data that enters an application comes from the outside world — forms, third-party APIs, user requests — and can never be trusted at face value. The layer that handles this is validation: checking data before it's processed or stored.
Episode 15 weaves together three complementary concepts: models for data structure in the database, schemas as a formal description of that structure, and validation as a runtime check of incoming data. You'll use Prisma for models and Zod for input validation in Express routes.
A good database model determines an application's long-term quality. The Prisma schema from episode 14 can be extended with relations — for example, one user has many articles:
model Pengguna {
id Int @id @default(autoincrement())
email String @unique
nama String
artikel Artikel[]
}
model Artikel {
id Int @id @default(autoincrement())
judul String
isi String
penulisId Int
penulis Pengguna @relation(fields: [penulisId], references: [id])
}The declaration artikel Artikel[] in the Pengguna model and penulis Pengguna @relation(...) in the Artikel model form a one-to-many relation. Migrations then translate this schema into tables. A good design prevents duplicate data and dangling relations.
When designing a schema, ask: what are the core entities, what are their relations, and which fields are mandatory? Add indexes for columns frequently used as filters. A schema that's too rigid is hard to change, but one that's too loose creates messy data — balance is the key.
Zod is a validation library popular in the TypeScript and Node.js ecosystems. You describe the expected data shape, and Zod checks real data against that description:
npm install zodnpm install zod adds a validator with automatic type inference. Zod integrates smoothly with Prisma and TypeScript, but also works without TypeScript.
import { z } from "zod";
const skemaPengguna = z.object({
nama: z.string().min(3).max(100),
email: z.string().email(),
umur: z.number().int().positive().optional(),
});z.object({...}) describes the expected object: nama must be a string with a length of 3 to 100, email must be in email format, and umur is optional, a positive integer. skemaPengguna.parse(data) throws an error if data violates the rules, while skemaPengguna.safeParse(data) returns a result without throwing.
Combine the Zod schema with Express routes through middleware:
app.post("/api/pengguna", (req, res) => {
const hasil = skemaPengguna.safeParse(req.body);
if (!hasil.success) {
return res.status(400).json({
error: "Data tidak valid",
detail: hasil.error.issues,
});
}
const pengguna = await prisma.pengguna.create({
data: hasil.data,
});
res.status(201).json(pengguna);
});skemaPengguna.safeParse(req.body) checks the request body. If it fails, a 400 response carries the validation issue details; if it succeeds, the clean data in hasil.data is passed to Prisma. This pattern guarantees the data reaching the database is already shaped per the schema.
The defense order matters: validate input at the edge of the application, not near the database. With route-level validation, bad requests are rejected earlier without wasting database resources. Prisma itself also validates on create, but earlier Zod validation means clearer, more controlled errors for API consumers.
The Prisma model and the Zod schema describe the same data from two sides: one for persistence, one for the API boundary. Ideally both stay aligned — required fields in Zod exist in the model, the types used match, and allowed relations don't violate database rules.
Keeping both in sync requires discipline, especially as a team grows. Some projects derive the Zod schema directly from the Prisma model to avoid duplication, while others accept duplication for clarity. Choose the approach your team can sustain.
Here's what to take away:
z.object description.safeParse returns a result without throwing an error.In the next episode, episode 16, we'll discuss caching, sessions, and server-side state — the difference between stateless and stateful applications, sessions with express-session, caching with Redis, and setting cache headers for HTTP responses. You'll learn to build a faster, more resource-efficient API.