Steve Kinney

Demonstration: Typing Express with Zod

Let’s add some schema validation to our Express application. We’ll start by sketching out our schemas.

const TaskSchema = z.object({
  id: z.string(),
  title: z.string(),
  description: z.string().optional(),
  completed: z.boolean(),
});

const NewTaskSchema = TaskSchema.omit({ id: true, completed: true });
const UpdateTaskSchema = TaskSchema.partial().omit({ id: true });

Validating Our Tasks from the Database

The first step is making sure what we’re getting back from the database is valid.

// Validate the task
const result = TaskSchema.parse(task);

return res.json(result);

It’ll technically blow up—but that’s because SQLite stores our completed boolean as either a 1 or a 0. Luckily, we can coerce it into what we’re expecting.

const TaskSchema = z.object({
  id: z.coerce.number(),
  title: z.string(),
  description: z.string().optional(),
  completed: z.coerce.boolean(),
});

We could also choose to shorten everything like this:

const task = TaskSchema.or(z.undefined()).parse(await getTask.get([id]));

Validating All the Tasks

We can do something similar to /tasks.

const TasksSchema = z.array(TaskSchema);

It’ll now look like this:

app.get('/tasks', async (req: Request, res: Response) => {
  const { completed } = req.query;
  const query = completed === 'true' ? completedTasks : incompleteTasks;

  try {
    const tasks = TasksSchema.parse(await query.all());
    return res.json(tasks);
  } catch (error) {
    return handleError(req, res, error);
  }
});

Adding Schema Validation to the Request Body

We can use NewTaskSchema here.

app.post('/tasks', async (req: Request, res: Response) => {
  try {
    const task = NewTaskSchema.parse(req.body);
    await createTask.run([task.title, task.description]);
    return res.sendStatus(201);
  } catch (error) {
    return handleError(req, res, error);
  }
});

Updating Tasks

Again, there are no suprises here.

app.put('/tasks/:id', async (req: Request, res: Response) => {
  try {
    const { id } = req.params;

    const previous = TaskSchema.parse(await getTask.get([id]));
    const updates = UpdateTaskSchema.parse(req.body);
    const task = { ...previous, ...updates };

    await updateTask.run([task.title, task.description, task.completed, id]);
    return res.sendStatus(200);
  } catch (error) {
    return handleError(req, res, error);
  }
});

Next

Last modified on .