We'll start with helper functions for responses and error handling, then implement them in a sample route file with multiple handlers.
Objective
- Standardize API Responses: Ensure every API response has a consistent format:
{
"success": true,
"message": "Operation completed successfully",
"data": []
}
- Implement Global Error Handling: Catch and handle errors (validation errors via Zod, general errors) with consistent formatting, ensuring server stability on errors.
Standard Response Format
We’ll start by creating a helper function to structure our responses. This function will accept data, a message, and a status code to standardize responses.
Create a response.ts
file in your lib
directory:
// lib/response.ts
import { NextResponse } from "next/server";
type ApiResponse<T> = {
success: boolean;
message: string;
data?: T;
};
// Helper function for successful responses
export function formatResponse<T>(data: T, message = "Operation completed successfully", status = 200) {
return NextResponse.json<ApiResponse<T>>(
{
success: true,
message,
data,
},
{ status }
);
}
// Helper function for error responses
export function formatErrorResponse(message = "An error occurred", status = 500) {
return NextResponse.json<ApiResponse<null>>(
{
success: false,
message,
data: null,
},
{ status }
);
}
Global Error Handler
Next, let’s create a global error handler to catch validation errors (using Zod) and general server errors, providing consistent messaging for each type.
Create error-handler.ts
in your lib
directory:
// lib/error-handler.ts
import { ZodError } from "zod";
import { formatErrorResponse } from "./response";
// Handles different error types
export function routeErrorHandler(error: unknown) {
if (error instanceof ZodError) {
// Handling Zod validation errors
const validationErrors = error.errors.map(err => err.message).join(", ");
return formatErrorResponse(validationErrors, 422);
} else if (error instanceof Error) {
// Handling generic errors
return formatErrorResponse(error.message, 500);
} else {
// Handling unknown errors
return formatErrorResponse("An unknown error occurred", 500);
}
}
Example File Structure
.
├── app
│ └── api
│ └── users
│ └── route.ts
├── lib
│ ├── error-handler.ts
│ └── response.ts
└── ...
Final Route Example
Below is a complete example of a route.ts
file with multiple API operations. Each operation uses formatResponse
for successful responses and routeErrorHandler
for errors, following our standardized approach.
app/api/users/route.ts
// app/api/users/route.ts
import { z } from "zod";
import { PrismaClient } from "@prisma/client";
import { formatResponse, formatErrorResponse } from "@/lib/response";
import { routeErrorHandler } from "@/lib/error-handler";
const prisma = new PrismaClient();
// Shared validation schema
const userSchema = z.object({
id: z.string().optional(),
name: z.string().min(1, { message: "Name is required" }),
email: z.string().email({ message: "Invalid email format" }),
});
// Insert a new user
export async function POST(req: Request) {
try {
const json = await req.json();
const validatedData = userSchema.omit({ id: true }).parse(json);
const user = await prisma.user.create({ data: validatedData });
return formatResponse(user, "User created successfully", 201);
} catch (error) {
return routeErrorHandler(error);
}
}
// Update an existing user
export async function PUT(req: Request) {
try {
const json = await req.json();
const validatedData = userSchema.parse(json);
const user = await prisma.user.update({
where: { id: validatedData.id },
data: validatedData,
});
return formatResponse(user, "User updated successfully", 200);
} catch (error) {
return routeErrorHandler(error);
}
}
// Delete a user by ID
export async function DELETE(req: Request) {
try {
const { id } = await req.json();
if (!id) {
return formatErrorResponse("User ID is required", 400);
}
await prisma.user.delete({ where: { id } });
return formatResponse(null, "User deleted successfully", 200);
} catch (error) {
return routeErrorHandler(error);
}
}
Explanation
-
POST Handler (Insert):
- Validates request data with
userSchema
and creates a new user. - Returns a success response with
formatResponse
.
- Validates request data with
-
PUT Handler (Update):
- Validates request data, including
id
, to update the specified user. - Uses
formatResponse
for a standardized success response.
- Validates request data, including
-
DELETE Handler (Delete):
- Accepts an
id
, validates its existence, and deletes the user. - Uses
formatResponse
to indicate successful deletion orformatErrorResponse
if the ID is missing.
- Accepts an
-
Error Handling:
- Each handler wraps operations in a
try-catch
block, delegating error handling torouteErrorHandler
, which processes both Zod validation errors and general errors in a consistent format.
- Each handler wraps operations in a
Top comments (0)