Next.js 13 middleware passes empty body to the API route

My Next.js middleware doesn’t pass the request body to the next API route correctly. The issue arises with the “hasRequiredFields” function within the middleware, which checks for required fields in the body. This problem occurs when this function is called before reaching the next API route, but it’s specific to the Netlify server, not my local setup.

This is the middleware.ts located in the root of the Next.js app:

import { NextRequest, NextResponse } from "next/server"

export default async function middleware(req: NextRequest) {
  const endpoint: Endpoint | undefined = endpoints.find(
    endPoint => endPoint.path === req.nextUrl.pathname
  )
  if (!endpoint) {
    return forbid("Path not found")
  }
  if (endpoint.requiredFields) {
    const hasFields = await hasRequiredFields(req, endpoint)
    if (!hasFields) {
      return forbid("Missing required fields")
    }
  }
  return allow()
}

const hasRequiredFields = async (req: NextRequest, endpoint: Endpoint) => {
  // When this is called, the API route (req.nextUrl) which is transferred by allow() method receives an empty body
  // This only happens on Netlify, not on localhost
  const body = await req.json()
  return !endpoint.requiredFields || endpoint.requiredFields.every(field => field in body)
}

const forbid = (message: string) => {
  return new NextResponse(JSON.stringify({ message }), {
    status: 403,
    headers: {
      "content-type": "application/json"
    }
  })
}

const allow = () => {
  return NextResponse.next({
    headers: {
      "content-type": "application/json"
    }
  })
}

export const config = {
  matcher: ["/api/foo/:path*"]
}

interface Endpoint {
  path: string
  requiredFields?: string[]
}

const endpoints: Endpoint[] = [
  {
    path: "/api/foo/bar",
    requiredFields: ["foo"]
  }
]

This is the the API route code:

import { NextApiRequest, NextApiResponse } from "next"
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  // "body" is empty on Netlify, when "hasRequiredFields" method is called in "middleware.ts
  // This only happens on Netlify, not on localhost
  const { body } = req
  res.status(200).json({ message: body })
}

export default handler

Site name: https://api-security--huudle.netlify.app/

Based on the discussion in the helpdesk:

The problem is you’re consuming the request body inside your middleware. As per the logs:

and the docs: Edge Functions API | Netlify Docs you need to pass the request again so that the request can be used. Unfortunately, NextResponse.next() only allows modifying headers: Functions: NextResponse | Next.js (nextjs.org) and doesn’t allow passing the request body. Thus, these two would not work together. I’d advise you to take a look at: Next.js Middleware on Netlify | Netlify Docs, which provides some extra support for handling the request body.

Can you tell me how I can see those logs? I attempted to use the ‘MiddlewareRequest’ object, but I encountered an error that says, MiddlewareRequest only works in a Netlify Edge Function environment. Does this mean I can only use the middleware within a Netlify Edge Function?

Those logs are visible in your Edge Function logs. In the left navbar, go to Logs > Edge Functions.

Could you share where you got that error? I tried your codebase myself and did not run into that.

Middleware runs on Netlify Edge Functions, so yes. You can use it on Lambda, but it’s not going to work as you expect, so we should not consider it valid here.

I’ve made updates to the code repository.

And this is a part of it:

import { NextRequest, NextResponse } from "next/server";
import { MiddlewareRequest } from "@netlify/next";

export default async function middleware(req: NextRequest) {
  const r = new MiddlewareRequest(req);
  const endpoint: Endpoint | undefined = endpoints.find(
    endPoint => endPoint.path === r.nextUrl.pathname
  )
  if (!endpoint) {
    return forbid("Path not found")
  }
  if (endpoint.requiredFields) {
    const hasFields = await hasRequiredFields(r, endpoint)
    if (!hasFields) {
      return forbid("Missing required fields")
    }
  }
  return allow()
}

Is it possible that only main branch function logs are visible? Because, I can’t see any logs in the branch I’m testing this.

Yes, it is possible to view function logs for branches other than the main branch.

To find functions on another deploy, you can use the search field at the top of the list on your site’s Functions page. You can start typing to jump to a particular branch, or find a Deploy Preview by number.

If you are looking for function logs for a specific deploy, you can access them by going to the Function logs tab of the Netlify Drawer in a collaborative Deploy Preview.

I was referring to the Edge Function log, not the Functions log; I am unable to select a branch for the Edge Functions log.

You can click on Edge Functions in the deploy card:

that would take you to the logs of that deploy.

Thank you for sharing the screenshot. Did you have a chance to review the updated codebase?