Skip to main content
Tutorial

Zod Schema Validation: How I Validate API Inputs on Every Project

5 min read

A full stack developer in Abu Dhabi shares how Zod schema validation simplified API error handling across 40+ UAE projects

TypeScriptZodAPI ValidationNext.jsUAE Development

Last month, I was debugging a failed database write in a UAE e-commerce app at 2 AM. The culprit? A product SKU field that allowed spaces, causing downstream inventory APIs to reject the payload. I used to check inputs with a mess of if-else statements until a fellow Abu Dhabi dev recommended Zod. Now it’s the third tool I install on any new TypeScript project.

Why I Stopped Writing Manual Validations

Before Zod, I'd write custom checks in Laravel controllers or React Native forms. It worked, but tracking edge cases across 12 API endpoints was a nightmare. With Zod, I define schemas once and reuse them client-side in Next.js forms or server-side in Express.js.

At Tawasul Limo, a UAE luxury limo booking platform I built using Laravel + Next.js, Zod caught 90% of invalid inputs before they hit the database. For example, validating license plate formats for Dubai and Riyadh clients required supporting both Arabic and English characters. Instead of writing two regex patterns, I created a z.union() that accepts either format:

ts
// License plate validation for Tawasul Limo
const plateSchema = z.union([
  z.string().regex(/^[\u0600-\u06FF0-9]{8}$/), // Arabic letters
  z.string().regex(/^[A-Z0-9]{8}$/), // English letters
])

Zod 3.21.1 made this way cleaner than parsing error stacks in vanilla TypeScript 5.1.

Integrating Zod with Express and Next.js

My standard Express.js setup uses Zod for route validation. I start by defining shared interfaces in /types:

ts
// src/types/bookings.ts
export interface BookingRequest {
  guestName: string;
  pickupLocation: string;
  dropoffLocation: string;
  vehicleType: 'sedan' | 'suv' | 'van';
}

Then create a schema file using Zod’s methods:

ts
// src/schemas/bookingSchema.ts
import { z } from 'zod';
export const bookingSchema = z.object({
  guestName: z.string().min(2),
  pickupLocation: z.string().max(100),
  dropoffLocation: z.string().max(100),
  vehicleType: z.enum(['sedan', 'suv', 'van']),
});

In the Express route, I wrap this in a middleware that sends a 400 error if validation fails:

ts
// src/routes/bookings.ts
app.post('/book', async (req, res) => {
  try {
    const validated = bookingSchema.parse(req.body);
    // proceed with valid data
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

I use the same schema in a Next.js form component for client-side validation without duplicating logic. Works like magic in Dubai-based projects where clients expect zero post-launch bugs.

One Time Zod Threw Me for a Loop

Last year, while building the Greeny Corner plant ID app with Expo SDK 54, I hit a wall. Zod’s error messages didn’t match the iOS keyboard inputs for Arabic text. After three hours of debugging, I realized I’d forgotten to enable ICU support in the app’s metro.config.js. Once fixed, it validated Arabic strings without question marks in the error logs.

Small lessons like this matter here in the UAE where startups demand both Arabic and English support. Never underestimate character encoding in API validations.

Best Practices I Follow Religiousl

  1. Never define schemas inline

Keep them in /src/schemas folders. A project with 20+ endpoints gets messy fast without organization.

  1. Use z.infer for DTOs

For the DAS Holding corporate website, I defined data transfer objects directly from Zod schemas instead of writing separate interfaces:

`ts

const userSchema = z.object({...});

type User = z.infer;

`

  1. Version schemas alongside APIs

Tuck v1 schemas in /src/v1/schemas when upgrading APIs. One logistics client in Jeddah wrecked their warehouse API by updating a Zod enum without versioning. Learned this the hard way.

  1. Validate environment variables

Use Zod to check secrets in .env files—this helps avoid the "it works on my machine" trap. I’ve written more about environment variables done right.

Frequently Asked Questions

How to handle async validations in Zod?

Use .refine() with a Promise. For example, checking if an email exists in the database:

ts
emailSchema: z.string().refine(async (email) => {
  const exists = await db.user.findUnique({ where: { email } });
  return !exists;
}, 'Email already taken');

Does Zod work with Node.js 18's native ES Modules?

Yes. Just install with npm install zod and import with import { z } from 'zod'. No Babel config needed—it's compatible out of the box.

Can I use Zod schemas in React Native forms?

Totally. Run the same validation logic on mobile using React Hook Form. I did this for Greeny Corner’s plant care app to keep input rules consistent across web and iOS.

How do Zod schema errors compare to Laravel’s Validator?

Zod throws structured errors with .path arrays and .message texts, which makes rendering field-specific errors in React easier. Laravel’s Validator provides richer rules like unique:users,email but requires PHP-specific syntax.


Zod changed how I build APIs, especially for UAE clients expecting bulletproof validation across Arabic and English. I’ve been shipping projects faster since 2021—from Tawasul Limo's multi-language forms to Greeny Corner’s AI plant ID API. If you’d like to talk through validation strategies for your next UAE or GCC project, book a free consultation or check out my portfolio for live examples.

S

Sarah

Senior Full-Stack Developer & PMP-Certified Project Lead — Abu Dhabi, UAE

7+ years building web applications for UAE & GCC businesses. Specialising in Laravel, Next.js, and Arabic RTL development.

Work with Sarah