npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@craftvalue/zod-express-middleware

v2.0.1

Published

Express middleware to validate requests using zod schema's.

Downloads

4

Readme

@craftvalue/zod-express-middleware

Middleware for express that uses zod to make requests type-safe.

Installation

This package relies on zod, express and @types/express. These have been added as peer dependencies so they can be upgraded independently of this package.

@craftvalue/zod-express-middleware can be installed using:

npm install @craftvalue/zod-express-middleware

Usage

This package provides the processRequest function, which can be used to validate the .body, .query and .params properties of an Express Request. Separate functions for each of these are also provided (processRequestBody, processRequestQuery and processRequestParams).

Basic example:

import { processRequest } from '@craftvalue/zod-express-middleware';
import { z } from 'zod';

// app is an express app
app.get(
  '/',
  processRequest({
    body: z.object({
      bodyKey: z.number(),
    }),
  }),
  (req, res) => {
    // req.body is now strictly-typed and confirms to the zod schema above.
    // req.body has type { bodyKey: number };
    return res.json({ message: 'Validation for body passed' });
  },
);

A full example of using processRequest in a tiny Express app:

Full example:

import express from 'express';
import { processRequest } from '@craftvalue/zod-express-middleware';
import { z } from 'zod';

// Create an express app
const app = express();

// Define an endpoint using express, zod and @craftvalue/zod-express-middleware
app.get(
  '/:urlParameter/',
  processRequest({
    params: z.object({
      urlParameter: z.string(),
    }),
    body: z.object({
      bodyKey: z.number(),
    }),
    query: z.object({
      queryKey: z.string().length(64),
    }),
  }),
  (req, res) => {
    // req.params, req.body and req.query are now strictly-typed and confirm to the zod schema's above.
    // req.params has type { urlParameter: string };
    // req.body has type { bodyKey: number };
    // req.query has type { queryKey: string };
    return res.json({
      message: 'Validation for params, body and query passed',
    });
  },
);

// Start the express app on port 8080
const PORT = 8080;
app.listen(PORT, () => {
  console.log(`Server running `);
});

The validate* functions do not modify the query, params or body of the Request object, they only check whether they are valid according to the provided schema's. If you want to use the result of the schema validation (for example, if you want to strip unknown keys), you can use the process* equivalents (ie. processRequest or processRequestBody). These functions also accept a ZodEffects object, which means you can use zod's built-in .transform method:

Zod transformation example:

import { processRequest } from '@craftvalue/zod-express-middleware';
import { z } from 'zod';

export const zodEffects = z
  .object({ jsonString: z.string() })
  .refine(
    (incomingData) => {
      try {
        return JSON.parse(incomingData.jsonString);
      } catch (error) {
        return false;
      }
    },
    {
      message: '.jsonString should be a valid JSON string.',
    },
  )
  .transform((incomingData) => {
    return z.object({ bodyKey: z.number() }).parse(JSON.parse(incomingData.afhandelingsData));
  });

// app is an express app
app.get(
  '/',
  processRequest({
    body: zodEffects,
  }),
  (req, res) => {
    // req.body is now strictly-typed and confirms to the zod schema above ( in the .transform method ).
    // req.body has type { bodyKey: number };
    return res.json({ message: 'Validation for body passed' });
  },
);

processRequest

This functions accepts an object containing three optional properties:

schemas: {
  params? : ZodSchema,
  query? : ZodSchema,
  body? : ZodSchema
}

Each is a ZodSchema, from the zod library. The processRequest function checks whether each of these is present and if so, it will use it validate the corresponding property on the Express Request object.

If validation passes, next will be called and your request body, query and params properties will be type-safe within the endpoint.

If validation fails, a HTTP 400 response with a list of validation errors will be send to the caller. The next function will not be called and the request will stop being processed further.

processRequestBody, processRequestQuery and processRequestParams

These three functions work exactly the same as processRequest, except they only validate a single property within the Express Request. The other, non-validated properties will have type any, as if they were not modified at all. Only an example is provided for processRequestBody, but processRequestQuery and processRequestParams work in the same manner.

Note: all properties absent from the schema will be removed from the corresponding object (body, params or query).

Example:

import { processRequestBody } from '@craftvalue/zod-express-middleware';
import { z } from 'zod';

// app is an express app
app.get(
  '/',
  processRequestBody(
    z.object({
      bodyKey: z.number(),
    }),
  ),
  (req, res) => {
    // req.body is now strictly-typed and confirms to the zod schema above.
    // req.body: { bodyKey: number } without extra keys
    return res.json({ message: 'Validation for body passed' });
  },
);

processRequest

This functions accepts an object containing three optional properties:

schemas: {
  params? : ZodSchema,
  query? : ZodSchema,
  body? : ZodSchema
}

Each is a ZodSchema or a ZodEffects object, from the zod library. The processRequest function checks whether each of these is present and if so, it will use it process the corresponding property on the Express Request object.

If validation passes, next will be called and your request body, query and params properties will be type-safe within the endpoint. The body, query and params object will contain the result of the (succesful) parsing by zod.

If validation fails, a HTTP 400 response with a list of validation errors will be send to the caller. The next function will not be called and the request will stop being processed further.

processRequestBody, processRequestQuery and processRequestParams

These three functions work exactly the same as processRequest, except they only process a single property within the Express Request. The other, non-processed properties will have type any, as if they were not modified at all. Only an example is provided for processRequestBody, but processRequestQuery and processRequestParams work in the same manner.

Example:

import { processRequestBody } from '@craftvalue/zod-express-middleware';
import { z } from 'zod';

// app is an express app
app.get(
  '/',
  processRequestBody(
    z.object({
      bodyKey: z.number(),
    }),
  ),
  (req, res) => {
    // req.body is now strictly-typed and confirms to the zod schema above.
    // req.body: { bodyKey: number };
    return res.json({ message: 'Validation for body passed' });
  },
);

sendError and sendErrors

These two functions can be used to send errors using an Express Response to the caller of an endpoint in the same format as the processRequest functions. The function accepts two parameters: an ErrorListItem and an Express Response. The ErrorListItem has type { type: 'Body' | 'Query' | 'Params', errors: ZodError }.

The example below uses sendError to emulate the functionality of processRequestBody. The sendErrors function does the same but accepts an array of ErrorListItem objects.

Example:

import { sendError } from '@craftvalue/zod-express-middleware';
import { z } from 'zod';

// app is an express app
app.get('/', (req, res) => {
  const zodSchema = z.object({ bodyKey: z.number() });
  const result = zodSchema.safeParse(req.body);
  if (!result.success) {
    return sendError({ type: 'Body', errors: result.error }, res);
  }
  return res.json({ message: 'Validation passed' });
});

TypedRequest

Besides exporting the above middleware functions, @craftvalue/zod-express-middleware also provided several typings for usage with Express requests. Typescript is able to automatically infer the types of your request body, query and params if your endpoint definition is placed in the same file as the validation middleware, as shown above. However, if the code for your endpoint is in a separate file, typings will not be automatically available. This is where the TypedRequest, TypedRequestBody etc. types come in: the typeof a ZodSchema can be passed into the TypedRequest, providing your function with typings. An example:

import { Response } from 'express';
import { TypedRequestBody } from '@craftvalue/zod-express-middleware';

// bodySchema is a ZodSchema, imported from another file.
import { bodySchema } from '../validation/requestSchemas';

// This is the endpoint code: it is not placed in the same file as the route definition and the validation middleware.
export async function endpointCode(req: TypedRequestBody<typeof bodySchema>, res: Response) {
  // req.body is now typed: use TypedRequestParams, TypedRequestQuery for params and query, or TypedRequest for multiple together.
  const typedBody = req.body;
  return res.json(typedBody);
}