Type Safety at Runtime with Zod
Introduction
Zod is a schema declaration and validation library led by TypeScript
Zod is used behind Astro, tRPC, React Hook Form, and VeeValidate. It is a simple yet powerful library worth understanding.
Why Do You Need Zod?
Typically, to validate the correctness of data, we use JSDoc or TypeScript for type annotations. Although TypeScript can help us check types before the program runs, there is still a possibility of runtime errors due to data not matching expectations after being compiled to JavaScript, usually stemming from external data (forms, URLs, LocalStorage, API responses). This is when we use Zod to assist in checking the types of data at runtime, achieving schema validation for data during execution.
Example: Third-Party API Data Validation
For example, a getPerson
function retrieves data for different individuals based on their ID. Even if we can annotate the return type of this function, we cannot be sure that the API response matches the Person
type defined in TypeScript:
interface Person = { name: string};
const getPerson = async (id: string): Person => { return await fetch('https://www.example.com/people/' + id + '.json') .then((res) => res.json());};
After using Zod, we can dynamically check whether the data returned from the API conforms to the Person
type, ensuring data correctness in both TypeScript and runtime:
import { z } from 'zod';const Person = z.object({ name: z.string(),});// TS type: const getPerson: (id: string) => Promise<string>const getPerson = async (id: string) => { const response = await fetch('https://www.example.com/people/' + id + '.json').then((res) => res.json()); const result = Person.parse(response); // After Zod validation, ensure data conforms to Person return result;};
try { getPerson(1);} catch (e) { console.error(e);}
Example: URL Parameter Validation
Recently, while using the Nuxt framework, I came across this article: Zod and Query String Variables in Nuxt. I tried inserting Zod into the middleware (code that runs before navigating to a specific page) to validate the correctness of user-provided URL query parameters, elegantly and succinctly solving the problem of validating user input!
// The page query that the user wants to go to must have order and filter is optional, otherwise redirect to the default path (query: { order: 'DESC' })import { z } from 'zod';definePageMeta({ middleware: defineNuxtRouteMiddleware((to) => { const querySchema = z .object({ order: z.enum(['ASC', 'DESC']), filter: z.enum(['CURRENT', 'PAST', 'CURRENT,PAST']).optional(), }) .strict(); const defaultQuery = { order: 'DESC' }; const targetPath = to.fullPath; try { querySchema.parse(to.query); } catch (e) { // console.error(e) return navigateTo({ path: targetPath, query: defaultQuery }); } }),});
Summary
From the above example, we can observe that zod has many validation methods available, such as z.string()
, z.number()
, z.boolean()
, etc. These methods can be combined to check whether the data meets the expected type, and you can freely define the messages thrown when an error occurs. For more information, you can refer to the Zod documentation and check the extended reading for more practical cases and exercises.
Extended Reading
- You Might Be Using Typescript Wrong… - Theo - t3.gg
- Zod - TotalTypeScript
- Zod Tutorial - All 10 places for Zod in your React / Next.js app - ByteGrad