Client & server-side validation with Zod.
Rak
Posted on July 6, 2023
Validating the data we receive from various sources is crucial. It's a common challenge faced when dealing with user input, external APIs, or data read from databases.
In the JavaScript world, we have several tools to help with data validation, but one that stands out is Zod - a library for creating, manipulating, and validating schemas.
In this blog, we'll dive into the details of creating schemas using Zod. By the end, you will be able to confidently use Zod to create and validate your data models.
As a side note, I spent a good portion of my career in the fintech space with onboarding applications - aligning validation was always finicky.
Let's get started!
What is Zod?
Zod is a JavaScript library that allows developers to build schemas for their data. It is simple yet powerful, providing a fluent API for constructing schemas and validating data. With Zod, you can create complex, nested object schemas with deep type inference, default values, transformations, and more.
Setting Up
Before we start creating schemas, you'll need to install Zod using npm with the following command:
npm install zod
Creating a Basic Schema
Let's start by creating a basic schema for a user. In this schema, a user will have a name, email, and age.
import { z } from 'zod';
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().min(0),
});
Here we define an object schema, userSchema
, where name
and email
are string fields, with email
being validated with the built-in email validator. The age
is a number field with a minimum value of 0.
Validating Data with the Schema
Now, let's use our schema to validate some data.
const userData = {
name: "John Doe",
email: "john.doe@example.com",
age: 30,
};
const result = userSchema.safeParse(userData);
if (!result.success) {
console.log(result.error);
} else {
console.log(result.data);
}
safeParse
is a method provided by Zod that returns an object. If the validation fails, success
will be false, and the error
object will contain information about what went wrong. If the validation passes, success
will be true, and data
will contain the validated data.
Advanced Schema
Zod also allows creating advanced schemas with optional fields, default values, and custom validation. Let's create an advanced user schema.
const advancedUserSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().min(0),
website: z.string().optional().url(),
bio: z.string().default("This user hasn't written their bio yet."),
});
In this advanced schema, the website
field is optional and must be a valid URL. The bio
field has a default value and will automatically be filled in if the user doesn't provide one.
Custom Validators
Zod is not limited to predefined validators; you can write your own custom validators using the .refine()
method.
const passwordSchema = z.string().refine(password => password.length >= 8, {
message: "Password must be at least 8 characters long",
path: ['password'], // indicates this error is about the password field
});
In this schema, the password
field must be a string that is at least 8 characters long. If it's not, Zod will throw an error with our custom message.
Client & Server-Side Validation
So far, we've covered creating basic and advanced schemas using Zod, as well as using custom validators.
Client-side and server-side validation with Zod can be implemented in quite a similar way because both are run on JavaScript-based environments. The only difference would be where and when the validation is performed.
The primary advantage of using Zod in both client and server-side is the DRY (Don't Repeat Yourself) principle. You can define your validation schemas once and use them on both ends. This ensures that your data is consistent and follows the same rules across your entire application.
Client-Side Validation
On the client-side, you will typically use Zod validation in response to user interaction. For example, you might validate form data when a user submits a form. Here's an example using React:
import { z } from 'zod';
import React, { useState } from 'react';
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().min(0),
});
function UserForm() {
const [formData, setFormData] = useState({ name: '', email: '', age: '' });
const [errors, setErrors] = useState({});
const handleSubmit = (event) => {
event.preventDefault();
const result = userSchema.safeParse(formData);
if (result.success) {
// Proceed with form submission
console.log(result.data);
} else {
// Handle validation errors
setErrors(result.error.formErrors.fieldErrors);
}
};
// The rest of your form goes here
}
In this example, when the form is submitted, we parse the form data using the userSchema
. If validation is successful, we continue with form submission; otherwise, we show the errors.
Server-Side Validation
On the server-side, you will typically use Zod validation in your route handlers, immediately after receiving a request. Here's an example using the Nitric framework:
import { api } from "@nitric/sdk";
import { z } from "zod";
const mainApi = api("main");
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().min(0),
});
mainApi.get("/user", async (ctx) => {
const result = userSchema.safeParse(ctx.req.json());
if (result.success) {
// Proceed with processing the request
ctx.res.status = 200;
ctx.res.json(result.data);
} else {
// Handle validation errors
ctx.res.status = 400;
ctx.res.json(result.error.formErrors.fieldErrors);
}
return ctx;
});
In this example, when a POST request is made to the "/user" endpoint, we parse the request body using the userSchema
. If validation is successful, we continue processing the request; otherwise, we return a 400 status code along with the validation errors.
curl -X GET -d '{"name":"John","email":"john@example.com","age":"abcd"}' -H 'Content-Type: application/json' http://localhost:4001/user
{"age":["Expected number, received string"]}%
Zod can be used for both client-side and server-side validation in JavaScript applications, providing a consistent validation experience across your application.
Make sure to always validate user data on the server-side, even if you have client-side validation, to ensure data integrity and security.
Posted on July 6, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.