DEV Community

Cover image for Simplifying Request Body Handling in Next.js 15
Saiful Islam
Saiful Islam

Posted on

Simplifying Request Body Handling in Next.js 15

When working with API routes in Next.js 15, handling different types of request payloads (JSON, FormData, and Raw text) can be frustrating. Developers often write repetitive code to extract data from req.json(), req.formData(), or req.text().

To simplify this process, we’ll create a set of utility functions that make request body handling more structured and error-proof. These functions will also integrate with the serverResponse function we built in our previous blog to maintain consistent API responses.


Why Use a Request Body Helper?

Manually parsing request bodies in every API route leads to redundant code.
Missing or invalid payloads can cause unexpected runtime errors.
Inconsistent error messages make debugging difficult.

✅ With reusable helpers, we ensure cleaner code, standardized error handling, and better DX (Developer Experience).


Building the Request Body Helper in Next.js 15

Step 1: Creating the ServerError Class

First, let’s define a custom error class to handle different request body errors:

export class ServerError extends Error {
  constructor(message: string, name: string) {
    super(message);
    this.name = name;
  }
}
Enter fullscreen mode Exit fullscreen mode

📌 This allows us to throw custom errors when request bodies are missing or invalid.


Step 2: Creating Utility Functions for Parsing Request Bodies

Now, we’ll write functions to handle different request body types.

import { ServerError } from "@/helpers/server-helper/ServerError";

export async function getJsonBodyData<T>(req: Request): Promise<T | null> {
  try {
    return req.json() as T | null;
  } catch {
    throw new ServerError("JSON Payload is required", "BodyError");
  }
}

export async function getFormData<T>(req: Request): Promise<T | null> {
  try {
    return req.formData() as T | null;
  } catch {
    throw new ServerError("FORM Payload is required", "BodyError");
  }
}

export async function getRawBody(req: Request): Promise<string> {
  try {
    return req.text();
  } catch {
    throw new ServerError("TEXT Payload is required", "BodyError");
  }
}
Enter fullscreen mode Exit fullscreen mode

✅ Each function automatically throws an error if the required payload is missing or invalid.
✅ Works with TypeScript for better type safety.


Step 3: Integrating with the serverResponse Function

In our previous blog, we built a reusable serverResponse function to ensure consistent API responses. Now, let’s use it in an API route.

import serverResponse from "@/helpers/serverResponse";
import { getJsonBodyData } from "@/helpers/request-helper";

export async function POST(req: Request) {
  try {
    const body = await getJsonBodyData<{ email: string; password: string }>(req);

    if (!body || !body.email || !body.password) {
      return serverResponse({
        success: false,
        message: "Missing required fields",
        status: 400,
      });
    }

    // Simulated user creation
    const user = { id: 1, email: body.email };

    return serverResponse({
      success: true,
      message: "User created successfully",
      data: user,
      status: 201,
    });
  } catch (error) {
    return serverResponse({
      success: false,
      message: error instanceof ServerError ? error.message : "Internal Server Error",
      error: error.name || "UnknownError",
      status: 500,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

📌 Now, all API routes will follow a structured error-handling pattern!


Conclusion

With this helper function setup:

✔️ Less boilerplate when handling request payloads.
✔️ Standardized error handling using ServerError.
✔️ Consistent API responses with serverResponse.

If you haven’t read the previous blog, I recommend checking it out first to see how serverResponse works!

💡 What’s your approach to handling API request payloads? Let me know in the comments!

Top comments (0)