Mastering Input Validation with Zod : A TypeScript Developer's Guide

Mastering Input Validation with Zod : A TypeScript Developer's Guide

·

4 min read

Hey there, TypeScript enthusiasts! 👋 Today, we're diving deep into the world of input validation with Zod, a game-changing library that's about to revolutionize your Node.js development experience. Buckle up for a journey into type-safe paradise!

What's Zod ?

Zod is a TypeScript-first schema validation library that's taking the Node.js world by storm. It uses static type inference to automatically generate validators from your TypeScript types. Say goodbye to manually writing validation rules and hello to type-safe bliss!

Getting Started with Zod

First things first, let's get Zod installed in your project. It's as easy as running:

npm install zod

Zod in Action: From Basic to Advanced

Basic User Validation Example

Let's start with a simple user object validation :

import { z } from 'zod';

const userSchema = z.object({
  name: z.string().min(2, "Name must be at least 2 characters"),
  age: z.number().min(18, "Must be at least 18 years old"),
  email: z.string().email("Invalid email address"),
  website: z.string().url().optional(),
});

type User = z.infer<typeof userSchema>; // Infer the TypeScript type from the schema

// Validate a user
const validUser = { name: "Alice", age: 30, email: "alice@example.com" };
console.log(userSchema.parse(validUser)); // Passes validation

const invalidUser = { name: "B", age: 17, email: "not-an-email" };
try {
  userSchema.parse(invalidUser);
} catch (error) {
  console.log(error.errors);
  // [
  //   { path: ['name'], message: 'Name must be at least 2 characters' },
  //   { path: ['age'], message: 'Must be at least 18 years old' },
  //   { path: ['email'], message: 'Invalid email address' }
  // ]
}

Advanced Validation: Nested Objects and Arrays

Zod shines when dealing with complex data structures :

const addressSchema = z.object({
  street: z.string(),
  city: z.string(),
  zipCode: z.string().regex(/^\d{5}$/, "Invalid ZIP code"),
});

const advancedUserSchema = userSchema.extend({
  addresses: z.array(addressSchema).min(1, "At least one address is required"),
  tags: z.array(z.string()).optional(),
});

type AdvancedUser = z.infer<typeof advancedUserSchema>;

const validAdvancedUser: AdvancedUser = {
  name: "Charlie",
  age: 25,
  email: "charlie@example.com",
  addresses: [
    { street: "123 Main St", city: "Anytown", zipCode: "12345" }
  ],
  tags: ["developer", "TypeScript"]
};

console.log(advancedUserSchema.parse(validAdvancedUser)); // Passes validation

Integrating Zod with Express

Now, let's see how Zod can make your Express APIs bulletproof:

import express from 'express';
import { z } from 'zod';

const app = express();
app.use(express.json());

const userSchema = z.object({
  name: z.string().min(2),
  age: z.number().min(18),
  email: z.string().email()
});

app.post('/users', (req, res) => {
  try {
    const validatedUser = userSchema.parse(req.body);
    // Input is valid, proceed with user creation
    res.status(201).json(validatedUser);
  } catch (error) {
    if (error instanceof z.ZodError) {
      res.status(400).json({ errors: error.errors });
    } else {
      res.status(500).json({ error: "Internal server error" });
    }
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));

Advanced Zod Techniques

Custom Validations

Extend Zod's built-in validations with your own custom logic:

const passwordSchema = z.string()
  .min(8, "Password must be at least 8 characters")
  .refine(
    (password) => /[A-Z]/.test(password) && /[a-z]/.test(password) && /[0-9]/.test(password),
    "Password must contain uppercase, lowercase, and numeric characters"
  );

const userWithPasswordSchema = userSchema.extend({
  password: passwordSchema
});

Zod Transforms

Use Zod to not just validate, but also transform your data:

const dateSchema = z.string().transform((str) => new Date(str));

const eventSchema = z.object({
  name: z.string(),
  date: dateSchema
});

console.log(eventSchema.parse({ name: "Conference", date: "2023-12-25" }));
// Outputs: { name: "Conference", date: Date object }

Why Zod is a Game-Changer

  1. Type Safety: Zod leverages TypeScript's type system, ensuring your runtime checks match your static types.
  2. Auto-completion: Your IDE will suggest valid schema properties as you type. No more guessing!
  3. Performance: Zod is blazingly fast, perfect for high-traffic APIs.
  4. Flexibility: From simple string validations to complex object shapes, Zod has got you covered.
  5. Inference: Use z.infer<typeof schema> to generate TypeScript types from your schemas.

Best Practices with Zod

  1. Define Schemas Once: Create reusable schemas for common data structures.
  2. Combine with TypeScript: Use z.infer to keep your types and validations in sync.
  3. Error Handling: Always wrap Zod validations in try-catch blocks for proper error handling.
  4. Custom Error Messages: Provide clear, user-friendly error messages in your schemas.
  5. Performance Optimization: For high-traffic APIs, consider caching parsed results.

Wrapping Up

Zod is more than just a validation library; it's a TypeScript developer's best friend. By seamlessly integrating with your existing TypeScript types, it ensures that your data validation is always in sync with your type definitions.

So, are you ready to say goodbye to runtime type errors and hello to bulletproof APIs? Give Zod a try in your next project, and experience the joy of truly type-safe development!

Remember, in the world of web development, validation is not just a best practice—it's a superpower. And with Zod, you're practically wearing a cape! 🦸‍♂️

Happy coding, and may your types always be valid! 🎉


If you found this tutorial helpful, consider buying the author a coffee: Buy Me A Coffee

For more in-depth information, check out the official Zod documentation.

Did you find this article valuable?

Support Ben ✨ by becoming a sponsor. Any amount is appreciated!