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;
}
🔹 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 });
}
🔹 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,
});
}
}
🚀 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)
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?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 :)
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.
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.