Using Zod With Next.js Web
What Is Zod?
Zod is a TypeScript-first schema declaration and validation library with static type inference.
With Zod, you can declare a validator once and Zod will automatically infer the static TypeScript type eliminating duplicative type declarations.
Zod Terms
Schema: The term "schema" is used to refer to any data type from a simple string to a complex nested object. Used synonymously with terms like "Shape" or "Structure".
Model: When we use the term "Model", we are referring to an object or data set that mirrors a Zod schema's shape and types.
Typical Uses For Zod
- Defining data schemas for others to use
- Validating input into a UI component
- Validating a REST API request response
- Creating mocked models for use in development (learn more about Zod Factories)
- Asserting schema conformity within unit tests
Where To Put Your Schema File
All schema files live within the Next.js monorepo's @schwab/schema package.
Schema is then placed within sub-directories to help further categorize the state & origin of the data they represent.
Pattern: /packages/schema/src/{State}/{Origin if applicable}
Examples
A schema representing a response payload of a story fetched from the "CMAP" api will live in the /schema/src/fetched/cmap directory.
While a schema representing a transformed story will live within the /schema/src/transformed directory.
Next.js Web Data States
| State | Definition |
|---|---|
| Fetched | This model state represents an original unaltered API response payload obtained via "fetch" request. |
| Transformed | The state of a "fetched" model after it has been transformed by methods located in the @schwab/transformer package. |
| Props | The shape of UI Component props. This is a "transformed" model with non applicable properties omitted. |
Creating A Zod Schema
New to Zod schema creation? Please refer to their official documentation here.
Naming Your Zod Schema
Next.js Zod schema naming convention: {State}{ModelName}Schema.ts
Schema files should always:
- Contain a model name
- Contain the models state (see schema states above)
- Appear in PascalCase / StudCase
- End with the word Schema
By following this pattern fellow Schwabies will be able to quickly recognize the model and model state that a schema represents without further exploration.
Example 1: DeSegmented DeckTile
A schema representing a de-segmented DeckTile model:
- "State" = "DeSegmented"
- "ModelName" = "DeckTile"
Resulting file name: DeSegmentedDeckTileSchema.ts
Example 2: Fetched Story Video
A schema representing a fetched story video model:
- "State" = "Fetched"
- "ModelName" = "StoryVideo"
Resulting file name: FetchedStoryVideoSchema.ts
Example 3: Transformed Car
A schema representing a transformed Car model (like Bumble Bee):
- "State" = "Transformed"
- "ModelName" = "Car"
Resulting file name: TransformedCarSchema.ts
Full Next.js Web Schema Example
Here's an actual Next.js Web Zod schema that is representing a story video that was fetched from the Drupal CMAP API.
File name: FetchedStoryVideoSchema.ts
File path: /home/nextjs-web/packages/schema/src/fetched/cmap/FetchedStoryVideoSchema.ts
import { z } from 'zod';
import { EFetchedRole } from '../../enums/EFetchedRole';
export const FetchedStoryVideoSchema = z.object({
role: z.literal(EFetchedRole.Video),
identifier: z.string(),
title: z.string(),
url: z.string(),
srt: z.string().nullable(),
transcript: z.string().nullable(),
aspectRatio: z.string(),
attribution: z.string().nullable(),
duration: z.string().nullable(),
poster: z.object({
title: z.string(),
url: z.string(),
altText: z.string().nullable(),
attribution: z.string().nullable(),
aspectRatio: z.string(),
disclosure: z.string().nullable(),
}),
caption: z.string().nullable(),
index: z.number(),
});
export type TFetchedStoryVideo = z.infer<typeof FetchedStoryVideoSchema>;
Static Type Inference
At the end of your Zod schema file, be sure to export your schema's inferred TypeScript type like so:
export type TFetchedStoryVideo = z.infer<typeof FetchedStoryVideoSchema>;
By doing this, TFetchedStoryVideo can now be used to set our story video transformer's "jsonAPIData" argument type.
Example: /home/nextjs-web/packages/transformer/src/utilities/map-video.ts
import { TFetchedStoryVideo } from '@schwab/schema/fetched/FetchedStoryVideoSchema';
import { TTransformedVideo } from '@schwab/schema/transformed/TransformedVideoSchema';
export const mapVideo = (
videoRefID: string,
jsonAPIData: TFetchedStoryVideo,
datasource: EDrupalSource,
videoObj?: { attributes: Record<string, Record<string, string | undefined>> },
): TTransformedVideo => {
// ... implementation
}
Start Generating Mocked Schema Models
Quickly mock your Zod schema for testing and development by creating a simple Zod Factory in the next section.