Validation using Middlewares and Yup

Using middlewares to validate Next.js API Routes.

Middlewares

withMethods

Let's start with a simple middleware to validate request.method.

import type { NextApiHandler, NextApiRequest, NextApiResponse } from "next"
 
export function withMethods(methods: string[], handler: NextApiHandler) {
  return async function (request: NextApiRequest, response: NextApiResponse) {
    if (!methods.includes(request.method)) {
      return response.status(405).json("")
    }
 
    return handler(request, response)
  }
}
import type { NextApiHandler, NextApiRequest, NextApiResponse } from "next"
 
export function withMethods(methods: string[], handler: NextApiHandler) {
  return async function (request: NextApiRequest, response: NextApiResponse) {
    if (!methods.includes(request.method)) {
      return response.status(405).json("")
    }
 
    return handler(request, response)
  }
}

To use this middleware, we call our API handler by wrapping it inside withMethods:

async function handler(request: NextApiRequest, response: NextApiResponse) {}
 
export default withMethods(["POST"], handler)
async function handler(request: NextApiRequest, response: NextApiResponse) {}
 
export default withMethods(["POST"], handler)

withValidation

Next, we're going to create a middleware to validate request.body

import type { NextApiHandler, NextApiRequest, NextApiResponse } from "next"
import type { ObjectShape, OptionalObjectSchema } from "yup/lib/object"
 
export function withValidation<T extends OptionalObjectSchema<ObjectShape>>(
  schema: T,
  handler: NextApiHandler
) {
  return async function (request: NextApiRequest, response: NextApiResponse) {
    try {
      request.body = await schema.validate(request.body)
 
      return handler(request, response)
    } catch (error) {
      if (error instanceof yup.ValidationError) {
        return response.status(422).json(error.message)
      }
 
      return response.status(422).json("")
    }
  }
}
import type { NextApiHandler, NextApiRequest, NextApiResponse } from "next"
import type { ObjectShape, OptionalObjectSchema } from "yup/lib/object"
 
export function withValidation<T extends OptionalObjectSchema<ObjectShape>>(
  schema: T,
  handler: NextApiHandler
) {
  return async function (request: NextApiRequest, response: NextApiResponse) {
    try {
      request.body = await schema.validate(request.body)
 
      return handler(request, response)
    } catch (error) {
      if (error instanceof yup.ValidationError) {
        return response.status(422).json(error.message)
      }
 
      return response.status(422).json("")
    }
  }
}

This middleware accepts a schema to validate request.body against. We use it as follows:

const postSchema = yup.object({
  title: yup.string().required("`Title` field missing."),
  body: yup.string().required("`Body` field missing"),
})
 
interface Post extends yup.TypeOf<typeof postSchema> {}
 
async function handler(request: NextApiRequest, response: NextApiResponse) {
  const post: Post = request.body
 
  // TODO: Save post.
 
  response.status(201).json("")
}
 
export default withValidation(postSchema, handler)
const postSchema = yup.object({
  title: yup.string().required("`Title` field missing."),
  body: yup.string().required("`Body` field missing"),
})
 
interface Post extends yup.TypeOf<typeof postSchema> {}
 
async function handler(request: NextApiRequest, response: NextApiResponse) {
  const post: Post = request.body
 
  // TODO: Save post.
 
  response.status(201).json("")
}
 
export default withValidation(postSchema, handler)

Chaining

We can call mutiple middlewares by chaining:

pages/api/posts
export default withMethods(["POST"], withValidation(postSchema, handler))
pages/api/posts
export default withMethods(["POST"], withValidation(postSchema, handler))

Code

You can check out the code here: https://github.com/shadcn/nextjs-middleware-validation.