I was building an API recently and hit a familiar wall. My routes were fast, but I kept finding bugs where the data coming in wasn’t what I expected. A query parameter was a string when it should have been a number. A request body was missing a required field. I was writing validation logic by hand and then writing the same types again in TypeScript. It felt messy and, frankly, a bit dangerous. What if my validation and my types got out of sync? That’s when I decided to look for a better way. I found it by bringing together two excellent tools: the Fastify web framework and the Zod validation library. The result was cleaner, safer, and far more enjoyable code.
Fastify is built for speed and a great developer experience. Zod lets you define what your data should look like with a simple, readable syntax. When you use them together, you define your data shape once. That single definition does two critical jobs: it validates incoming requests at runtime, and it gives you perfect TypeScript types for free. No more duplication. No more drift between your validation rules and your code’s expectations.
Think about a user registration endpoint. You need an email, a password, and maybe a name. Without a structured approach, you might check for these fields in your route handler. That code gets bulky fast. With Fastify and Zod, you define the shape of the data up front, and Fastify handles the check before your main code even runs.
Here’s how simple it is to set up. First, you’ll need to install the integration package that bridges the two worlds: @fastify/zod. This plugin teaches Fastify how to understand Zod schemas.
npm install fastify @fastify/zod zod
Now, let’s look at a basic example. We’ll create a schema for a blog post and use it in a route.
import fastify from 'fastify';
import { z } from 'zod';
import { serializerCompiler, validatorCompiler } from '@fastify/zod';
// Create the app
const app = fastify();
// Set up the Zod compilers. This is the magic glue.
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);
// Define what a blog post looks like, once and for all.
const PostSchema = z.object({
title: z.string().min(5),
content: z.string(),
published: z.boolean().default(false),
tags: z.array(z.string()).optional()
});
// This is the powerful part: get a TypeScript type from the schema.
type PostInput = z.infer<typeof PostSchema>;
// Use the schema in a route. Fastify validates the request body against it.
app.post('/posts', {
schema: {
body: PostSchema,
},
}, async (request, reply) => {
// `request.body` is now fully typed as `PostInput`.
// It has already been validated by Zod.
const newPost = request.body; // Type is { title: string, content: string, published: boolean, tags?: string[] }
// Your business logic here, confident in the data shape.
reply.code(201).send({ id: 1, ...newPost });
});
See what happened? We wrote PostSchema. Zod then created the PostInput type for us. We attached the schema to the route, and Fastify ensured only valid data gets to the handler. The request.body inside the handler is perfectly typed. If someone sends a post with a 2-character title or a number for published, Fastify automatically sends a clear 400 error response. Your core function never sees the bad data.
But what about other parts of a request? It’s not just the body. You can validate query strings, URL parameters, and headers with the same ease. This is where the approach really shines for building complete, reliable APIs.
const GetPostsSchema = z.object({
authorId: z.string().uuid(),
limit: z.coerce.number().int().min(1).max(100).default(20), // coerce string "10" to number 10
publishedOnly: z.enum(['true', 'false']).transform(val => val === 'true').optional()
});
app.get('/posts', {
schema: {
querystring: GetPostsSchema,
},
}, async (request, reply) => {
// request.query is typed and validated.
// `limit` is a number, `publishedOnly` is a boolean | undefined.
const filters = request.query;
// ... fetch posts from database
});
Notice the coerce and transform methods? Zod’s real power is in these data transformations. HTTP requests send everything as strings. Zod can not only check the data but also intelligently convert it into the formats your business logic needs. This keeps your route handlers clean and focused on their actual job, not data munging.
So, why does this combination feel so good? It closes a loop. As a developer, you describe your contract—what your API expects—in one place. The system then enforces that contract at the boundary (validation) and reflects it across your codebase (type safety). Your editor’s autocomplete becomes a guide, showing you exactly what data you have to work with. Refactoring becomes safer because if you change a schema, your types update instantly, and TypeScript will show you every piece of code that needs attention.
Have you ever spent time debugging an issue only to find it was a simple type mismatch that happened five function calls ago? This integration pushes those errors to the surface, right at the point where the data enters your system. It turns runtime surprises into compile-time errors.
Getting started is straightforward. Begin by defining Zod schemas for your core data objects. Then, gradually add them to your existing Fastify routes. You’ll immediately see the benefit in the clarity of your handlers. The @fastify/zod plugin handles the integration seamlessly, so you can focus on what your data should be, not on endlessly checking what it is.
I moved from seeing request validation as a chore to seeing it as the foundation of a robust application. It saves time, prevents bugs, and makes the code a pleasure to read. If you’re building APIs with Fastify and TypeScript, this pairing isn’t just convenient; it’s a strategic choice for quality. Give it a try on your next endpoint. You might be surprised how much mental overhead it removes.
What was the last data-related bug you faced in your API? Could a single source of truth for your data shape have prevented it? I’d love to hear about your experiences. If you found this walk-through helpful, please share it with a fellow developer who might be wrestling with the same challenges. Drop a comment below if you have questions or your own tips for building type-safe backends.
As a best-selling author, I invite you to explore my books on Amazon. Don’t forget to follow me on Medium and show your support. Thank you! Your support means the world!
101 Books
101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.
Check out our book Golang Clean Code available on Amazon.
Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!
📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!
Our Creations
Be sure to check out our creations:
Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools
We are on Medium
Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva