DEV Community

Cover image for 🚀 Building a Robust API Response Helper in Next.js 15
Saiful Islam
Saiful Islam

Posted on

🚀 Building a Robust API Response Helper in Next.js 15

When developing APIs in Next.js 15, structuring responses properly is crucial for consistency, debugging, and maintainability. Instead of manually crafting responses in every route, we can create a universal response helper that ensures uniformity across all API responses.

In this guide, we'll build a reusable API response helper using TypeScript, Next.js 15, and Zod for validation handling.


🔍 Why Use a Response Helper?

Ensures consistency – Every API response follows the same structure
Improves readability – Cleaner, more structured API responses
Simplifies error handling – Centralized handling of validation and server errors
Reduces redundancy – Write less repetitive response code


🛠 Creating the Response Helper

We'll create a utility function that:
✔ Returns a consistent response format
✔ Supports Zod validation errors
✔ Handles success, errors, and status codes dynamically

📌 Step 1: Define TypeScript Types

We need to define strongly typed response structures using TypeScript.

import type { ZodIssue } from "zod";

export type ValidationError = {
  field: string;
  message: string;
};

export interface ServerResponseType<T> {
  success: boolean;
  message?: string | undefined;
  error?: string | undefined | ZodIssue[] | ValidationError[];
  data?: T | undefined;
  status?: number | undefined;
}
Enter fullscreen mode Exit fullscreen mode

🔹 Key Features:

success: Indicates whether the request was successful
message: A human-readable response message
error: Supports custom validation errors or Zod validation issues
data: Stores the actual response data
✔ status: Allows custom HTTP status codes


📌 Step 2: Implement the Helper Function

Now, let's create the API response helper:

import type { ServerResponseType } from "@/types";
import { NextResponse } from "next/server";

/**
 * A universal API response helper function for Next.js 15.
 * @param {ServerResponseType<T>} options - API response parameters.
 * @returns {NextResponse<ServerResponseType<T>>}
 */
export default function serverResponse<T>({
  success,
  message = undefined,
  error = undefined,
  data = undefined,
  status = 200,
}: ServerResponseType<T>): NextResponse<ServerResponseType<T>> {
  const response: ServerResponseType<T> = { success, status };

  if (message) response.message = message;
  if (error) response.error = error;
  if (data) response.data = data;

  return NextResponse.json(response, { status: response.status });
}
Enter fullscreen mode Exit fullscreen mode

🔹 How It Works:

✅ Accepts a generic type <T> for flexible data handling
✅ Automatically formats the response into JSON
✅ Supports custom status codes and error messages
✅ Returns a structured API response


📌 Step 3: Using the Helper in Next.js 15 API Routes

Let's integrate this helper into an API route in Next.js 15.

✅ Example: User Registration API

Here's how we use serverResponse in a Next.js API route:

import { NextRequest } from "next/server";
import serverResponse from "@/utils/serverResponse";
import db from "@/lib/db";
import { z } from "zod";

const userSchema = z.object({
  name: z.string().min(2, "Name must be at least 2 characters"),
  email: z.string().email("Invalid email"),
  password: z.string().min(6, "Password must be at least 6 characters"),
});

export async function POST(req: NextRequest) {
  try {
    const body = await req.json();

    // Validate input data
    const validation = userSchema.safeParse(body);
    if (!validation.success) {
      return serverResponse({
        success: false,
        error: validation.error.issues,
        status: 400,
      });
    }

    // Check if user already exists
    const existingUser = await db.user.findUnique({
      where: { email: body.email },
    });

    if (existingUser) {
      return serverResponse({
        success: false,
        message: "User already exists",
        status: 409,
      });
    }

    // Create new user
    const user = await db.user.create({
      data: {
        name: body.name,
        email: body.email,
        password: body.password, // In real apps, always hash passwords!
      },
    });

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

🚀 Final Thoughts

With this reusable response helper, Next.js 15 API routes become cleaner, more structured, and easier to manage. It simplifies error handling, ensures consistency, and helps in debugging.

Would you use this approach in your Next.js APIs? Let me know your thoughts! 🚀

Top comments (4)

Collapse
 
brense profile image
Rense Bakker

Why do you want to know success:true? Seems like redundant information if the http status code already indicates success or failure... You could check if the status code is >= 400 I think?

Collapse
 
varundeva profile image
Varun Deva

True
In some cases i do check response body having any boolean like this just to show success toast or loading state handling etc
Otherwise its not mandatory :)

In one way its good to remove redundant information from response body to save the network bandwidth. If your api will get the millions of requests then a small bytes also values more :)

Collapse
 
saiful7778 profile image
Saiful Islam

That great. The developer can check the response via the HTTP status code. But sometimes we need a boolean value, Is this a success or not? In my experience, I have faced some issues when working with Flutter developers they want this success value. So I added this success value universal, if developers need this then they can access it as well.

Collapse
 
saiful7778 profile image
Saiful Islam

Can we connect on Linkedin. Here is my Linkedin account

Some comments may only be visible to logged-in visitors. Sign in to view all comments.