zod-cru is a small TypeScript library to create zod validators that have some differences depending whether a new object is created or an existing object is being retrieved or updated. “CRU” stands for create, read and update (like CRUD without delete).
A common example use case would be an API where the creation request for an object does not include an object ID (since that is generated by the server) and some properties are required, while others are optional, leading them to be initialized with certain default values. When you retrieve the created object, it has an ID and all the properties are defined. In a request to update the object, the ID is required to identify the object, but all other properties are optional, you need to specify only those that you want to change. The shape of the object has similarities but also differences in all 3 modes.
Commonly, a single TypeScript type is used for all 3 modes and type casts or the !
operator are used to handle the differences. zod-cru provides convenience functions to create proper typings and zod validators for the 3 modes, without having to duplicate each property 3 times. This is especially useful in applications where both the client and the server are written in TypeScript and can share the typings.
zod-cru is available through NPM. Install it using npm install -S zod-cru
or yarn add zod-cru
. zod
is a peer dependency, so you must add it to your dependencies as well.
It is also available on JSR and can be installed using jsr add @cdauth/zod-cru
. When using it through JSR, you need to import @cdauth/zod-cru
instead of zod-cru
in all the examples below.
zod-cru is published as an ECMAScript module. If you want to use it in a Node.js project that still uses CommonJS, you can use dynamic import()
to import the library. Since Node.js 23, it is also supported to require()
ES modules.
import { cruValidator, onlyRead, optionalCreate, optionalUpdate, type CRU, type CRUType } from "zod-cru";
import * as z from "zod";
const myObjectValidator = cruValidator({
id: onlyRead(z.number()),
name: optionalUpdate(z.string()),
colour: optionalCreate(z.string(), "#000000"),
fields: {
create: z.array(myFieldValidator.create).default(() => []),
read: z.array(myFieldValidator.read),
update: z.array(myFieldValidator.update).optional()
}
});
type MyObject<Mode extends CRU = CRU.READ> = CRUType<Mode, typeof myObjectValidator>;
The function cruValidator()
accepts an object where each property is either a zod validator (meaning that the property will be the same in all 3 modes) or an object of the shape { create?: z.ZodTypeAny; read?: z.ZodTypeAny; update?: z.ZodTypeAny }
(defining the validators for the 3 modes, with an undefined validator meaning that the property is not present in that mode). The latter can be created in a convenient and readable way using the functions onlyCreate()
, onlyRead()
, onlyUpdate()
, exceptCreate()
, exceptRead()
, exceptUpdate()
, optionalCreate()
and optionalUpdate()
.
myObjectValidator
contains the validators and is an object of the shape { create: z.ZodTypeAny; read: z.ZodTypeAny; update: z.ZodTypeAny }
. To validate the create object, you can use myObjectValidator.create.parse(input)
, and same with the other modes.
The type MyObject
provides the typings. It exists in 5 different shapes:
Type | Description | Typing |
---|---|---|
|
The shape of a create request for the object. |
{
name: string;
colour?: string;
fields?: MyField<CRU.CREATE>[];
} |
|
The shape of a validated create request for the object as returned by |
{
name: string;
colour: string;
fields: MyField<CRU.CREATE_VALIDATED>[];
} |
|
The shape of the object when retrieving it. |
{
id: number;
name: string;
colour: string;
fields: MyField[];
} |
|
The shape of a update request for the object. |
{
id: number;
name?: string;
colour?: string;
fields?: MyField<CRU.UPDATE>[];
} |
|
The shape of a validated update request for the object as returned by |
{
id: number;
name?: string;
colour?: string;
fields?: MyField<CRU.UPDATE_VALIDATED>[];
} |
cruValidator(declaration)
Creates validators for the 3 modes from the provided object shape.
Parameters:
declaration
(Record<any, z.ZodTypeAny | { create?: z.ZodTypeAny; read?: z.ZodTypeAny; update?: z.ZodTypeAny }>
): The shape of the object. Each property can be either a zod validator (meaning that the same validator will apply to all 3 modes), or the result of one of the functionsonlyCreate()
,onlyRead()
,onlyUpdate()
,exceptCreate()
,exceptRead()
,exceptUpdate()
,optionalCreate()
andoptionalUpdate()
, or for full control an object containing acreate
,read
and/orupdate
property, each containing a validator. In the latter case, if a certain mode is not defined, the property will be absent in that mode.
Returns an object with properties create
, read
and update
, each one containing a zod validator for the respective mode.
enum { READ, CREATE, CREATE_VALIDATED, UPDATE, UPDATE_VALIDATED }
Used as the generic for the CRUType
type.
CRUType<Mode extends CRU, Validator extends ReturnType<typeof cruValidator>>
Infers the types for the different modes for a validator defined using cruValidator()
. Usually used in the following way:
const myObjectValidator = cruValidator(...);
type MyObject<Mode extends CRU = CRU.READ> = CRUType<Mode, typeof myObjectValidator>;
onlyCreate(validator)
onlyRead(validator)
onlyUpdate(validator)
When used for a property passed to cruValidator()
, the property will only be present in the specified mode and use the given zod validator there.
exceptCreate(validator)
exceptRead(validator)
exceptUpdate(validator)
When used for a property passed to cruValidator()
, the property will only be present in the other two modes and use the given zod validator there.
optionalCreate(validator, defaultValue?)
Marks the property as optional in create and update mode. When used for a property passed to cruValidator()
, the property will behave the following way in the different modes:
- In create mode, the property is optional. If the property is not specified, it will be set to the given default value (or undefined if no default value is set).
- In read mode, the property is required.
- In update mode, the property is optional. If the property is not specified, it will be absent from the update request.
Parameters:
validator
(z.ZodTypeAny
): A zod validatordefaultValue
(any | () => any
, optional): An optional default value for the property in create mode. Defaults toundefined
. A function can be specified to generate a new default value each time. This is commonly used when the default value is an object or array, to make sure that a new one is created for each object.
optionalUpdate(validator)
When used for a property passed to cruValidator()
, the property will be optional in update mode but required in create and read mode.