Zod - TypeScript-first schema declaration and validation library #10

Zod - TypeScript-first schema declaration and validation library #10


We will be looking at techniques for refactoring working code to remove duplication.

Here we have schemas for User, Post, and Comment:

const User = z.object({
  id: z.string().uuid(),
  name: z.string(),
})

const Post = z.object({
  id: z.string().uuid(),
  title: z.string(),
  body: z.string(),
})

const Comment = z.object({
  id: z.string().uuid(),
  text: z.string(),
})

Notice that id is present in each.

Zod provides us with various ways of composing objects together into different types, allowing us to DRY out our code.

👉 Challenge:

We will use Zod to refactor the above code to remove the id duplication.

👉 Solution:

There are a bunch of ways that this code could be refactored.

For reference, here is what we started with:

const User = z.object({
  id: z.string().uuid(),
  name: z.string(),
})

const Post = z.object({
  id: z.string().uuid(),
  title: z.string(),
  body: z.string(),
})

const Comment = z.object({
  id: z.string().uuid(),
  text: z.string(),
})

The Simple solution:

The simplest solution is to strip out the id into its type. From there, each of the z.objects could reference it:

const Id = z.string().uuid();

const User = z.object({
  id: Id,
  name: z.string(),
})

const Post = z.object({
  id: Id,
  title: z.string(),
  body: z.string(),
})

const Comment = z.object({
  id: Id,
  text: z.string(),
})

This is pretty good, but id: ID is still being repeated. All of the cases are still passing, so that is okay.

Use the Extend Method:

Another solution would be to create a base object called ObjectWithId. This base object will contain our id:

const ObjectWithId = z.object({
  id: z.string().uuid(),
})

From there, we can use the extend method to create new schemas that add on to the base object:

const ObjectWithId = z.object({
  id: z.string().uuid(),
})

const User = ObjectWithId.extend({
  name: z.string(),
})

const Post = ObjectWithId.extend({
  title: z.string(),
  body: z.string(),
})

const Comment = ObjectWithId.extend({
  text: z.string(),
})

Note that .extend() will overwrite fields!

Use the Merge Method:

Similar to the above solution, we could use the merge method to extend the ObjectWithId base object:

const User = ObjectWithId.merge(
  z.object({
    name: z.string(),
  }),
)

Using .merge() is slightly more verbose than .extend(). We have to pass in a z.object() that contains the name z.string().

Merging is generally used when two different types are being combined, rather than just extending a single type.

Summary:

Those are a few different ways that you can compose objects together in Zod to reduce the amount of code duplication, make things more DRY, and make things a bit more maintainable!


I hope you found it useful. Thanks for reading. 🙏

Let’s get connected! You can find me on: