Published on

Using Zod to Create Schema with Existing Types

Authors
  • Name
    Ripal & Zalak
    Twitter

Using Zod to Create Schema with Existing Types

When working with APIs, it’s often necessary to validate incoming data against a predefined type. Zod, a powerful TypeScript schema declaration and validation library, allows you to achieve this with ease. However, what happens when you want to create a Zod schema based on an existing type, such as Axios' Method? Let's dive into it!

Problem Statement

Suppose you have an endpoint that accepts a parameter method that must conform to the Method type from Axios:

export type Method =
  | 'get'
  | 'GET'
  | 'delete'
  | 'DELETE'
  | 'head'
  | 'HEAD'
  | 'options'
  | 'OPTIONS'
  | 'post'
  | 'POST'
  | 'put'
  | 'PUT'
  | 'patch'
  | 'PATCH'
  | 'purge'
  | 'PURGE'
  | 'link'
  | 'LINK'
  | 'unlink'
  | 'UNLINK'

You want to ensure that your schema validation matches this type exactly, avoiding redundant declarations or mismatches. How do you achieve this in Zod?

Solution

Using z.enum with Explicit Values

The simplest approach is to recreate the Method type as an array of string literals and use z.enum:

import { z } from 'zod'

const MethodSchema = z.enum([
  'get',
  'GET',
  'delete',
  'DELETE',
  'head',
  'HEAD',
  'options',
  'OPTIONS',
  'post',
  'POST',
  'put',
  'PUT',
  'patch',
  'PATCH',
  'purge',
  'PURGE',
  'link',
  'LINK',
  'unlink',
  'UNLINK',
])

While effective, this approach requires manually synchronizing the values, which may lead to errors if the Method type changes in Axios.

Inferring from Existing Types

A more dynamic and TypeScript-friendly approach is to infer the schema directly from the Method type. You can achieve this by using Zod’s z.enum combined with a runtime array:

import { z } from 'zod'
import type { Method } from 'axios'

const methods = [
  'get',
  'GET',
  'delete',
  'DELETE',
  'head',
  'HEAD',
  'options',
  'OPTIONS',
  'post',
  'POST',
  'put',
  'PUT',
  'patch',
  'PATCH',
  'purge',
  'PURGE',
  'link',
  'LINK',
  'unlink',
  'UNLINK',
] as const

const MethodSchema = z.enum(methods)

Here, the as const assertion ensures that TypeScript infers the correct literal types, enabling Zod to validate against them.

Using z.custom for Custom Types

Zod’s z.custom method can validate against a custom TypeScript type:

import { z } from 'zod'
import type { Method } from 'axios'

const MethodSchema = z.custom<Method>((value) => {
  return typeof value === 'string' && methods.includes(value as Method)
})

This approach validates against the Method type while keeping the implementation in sync with the runtime values.

Complete Example

Here’s how you can use the MethodSchema in a complete schema for validating API requests:

import { z } from 'zod'

const ApiRequestSchema = z.object({
  method: MethodSchema,
  url: z.string().url(),
  headers: z.record(z.string()).optional(),
})

// Example usage
const requestData = {
  method: 'GET',
  url: 'https://api.example.com',
  headers: {
    Authorization: 'Bearer token',
  },
}

try {
  ApiRequestSchema.parse(requestData)
  console.log('Validation successful!')
} catch (err) {
  console.error('Validation failed:', err.errors)
}