CRUD API with Validation
Part of the Full Stack Developer Roadmap
In short
An API that accepts anything is an API that breaks. This project adds proper validation and error handling, so bad data is rejected at the door with a clear reason your React forms can show.
A more complete API with proper request validation and error handling — the direct backend counterpart to the React forms you'll build in Phase 9.

Think of it like this
The courier booking counter
The courier counter weighs your parcel, checks the PIN code and refuses anything over the limit before it enters the system. A wrong address gets fixed at the counter, not discovered three cities later. Validation is that counter for your API.
Watch it happen
Bad note, good note
POST {title:""}
1/7The user submits a note with an empty title.
The big ideas
What you need to know
Validate at the edge
Check req.body, req.params and req.query as soon as the request arrives, before any database call.
Parse, don't just check
safeParse returns cleaned data: trimmed strings, defaults filled in, unknown fields dropped. Save that, not req.body.
PATCH uses a partial schema
Updates send only changed fields. Schema.partial() makes every field optional but still type-checks what arrives.
Field errors help the UI
Return { errors: { title: ["Title is required"] } } so each message can sit under its own input.
See it in code
Walk through the code
1import { z } from "zod";2 3const NoteInput = z.object({4 title: z.string().trim().min(1, "Title is required").max(120),5 body: z.string().max(5000).default(""),6 tags: z.array(z.string()).max(5).optional(),7});8 9app.post("/api/notes", async (req, res) => {10 const parsed = NoteInput.safeParse(req.body);11 if (!parsed.success) {12 return res.status(400).json({ errors: z.flattenError(parsed.error).fieldErrors });13 }14 const note = await db.note.create({ data: parsed.data });15 res.status(201).json(note);16});1/3The schema describes a valid note: a trimmed title up to 120 chars, optional body and up to five tags.
This uses Zod 4. The same NoteInput schema can be imported by your React form too.
Save yourself the pain
Common mistakes
Oops
Passing req.body straight into db.create(), so a user can send isAdmin: true.
Do this instead
Save only the parsed data. Zod objects drop unknown keys by default, so extra fields never reach the DB.
Oops
Returning 500 for bad input because the database threw an error.
Do this instead
Catch bad input with validation and return 400. A 500 should mean your code broke, not the user's.
Your mission
First milestone: validated notes
Quick check
A client sends { title: "Hi", role: "admin" } to your Zod-validated route. What ends up in parsed.data?
Go deeper
- 1Add full validation to every write endpoint (create/update).
- 2Return field-specific validation errors in a consistent shape.
- 3Write a few tests confirming invalid requests are correctly rejected.
More projects 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.