Error Handling

Error Handling 🚨

We know how complicating error handling can be, which is why we've taking the time to make this process seemlessly easy for you to work with.

Veloz kits comes with built-in error handlers done the right way for you hackers.

The codebase contain some reusable utilities functions which tend to make this process a lot easier for you to work with.

Server Side Error Handling ☁️

Next.js App Router (API Routes)

We've taken the time to create a custom error handler for you to work with, be it VALIDATION_ERROR, CUSTOM_ERROR ..etc let take a look at some of them.

Custom Error

A custom error class that extends the default Error class called HttpException, this is used to throw custom errors in your codebase and can be found in the /src/app/api/utils/exception.(ts) file.

exception.ts

_14
_14
import { RESPONSE_CODE } from "@/api/types";
_14
_14
export default class HttpException extends Error {
_14
public code: RESPONSE_CODE;
_14
public statusCode: number;
_14
constructor(code: RESPONSE_CODE, message: string, statusCode: number) {
_14
super();
_14
this.name = "HttpException";
_14
this.code = code;
_14
this.message = message;
_14
this.statusCode = statusCode;
_14
}
_14
}

The code is self-explanatory, we're just extending the default Error class and adding some extra properties to it, which we'll be using later on. Whenever there's a need to throw an error to the client from the server, you simply invoke the class and pass in the required parameters.


_10
throw new HttpException(
_10
RESPONSE_CODE.USER_ALREADY_EXIST, // error code (meant for debugging purpose)
_10
"User already exist", // message
_10
400 // status code
_10
);

You would notice the code property which is of type RESPONSE_CODE, this is an enum that contains all the possible error codes that can be thrown from the server, you can find it in the /src/app/api/types/index.ts file.

types/index.ts

_28
export enum RESPONSE_CODE {
_28
// Common Responses code
_28
INVALID_FIELDS,
_28
USER_NOT_FOUND,
_28
USER_ALREADY_EXIST,
_28
INVALID_EMAIL,
_28
INVALID_LOGIN_CREDENTIALS,
_28
INTERNAL_SERVER_ERROR,
_28
VALIDATION_ERROR,
_28
NOT_ELIGIBLE,
_28
INVALID_PARAMS,
_28
METHOD_NOT_ALLOWED,
_28
ORDER_EXISTS,
_28
CHECKOUT_ERROR,
_28
ORDER_NOT_CREATED,
_28
SIGNUP_SUCCESSFULL,
_28
LOGIN_SUCCESSFULL,
_28
UNAUTHORIZED,
_28
FORBIDDEN,
_28
SUCCESS,
_28
INVALID_TOKEN,
_28
PLAN_NOT_FOUND,
_28
USER_ALREADY_SUBSCRIBED,
_28
ERROR,
_28
SUBSCRIPTION_NOT_FOUND,
_28
ACCOUNT_ALREADY_LINKED,
_28
EMAIL_FAILED_TO_SEND,
_28
}

But, How then does the error get handled? well, good question my friend.

A custom error handler middleware called CatchError() is used to handle all errors thrown from the server, this middleware can be found in the src/app/api/utils/_error.(ts) file. Notice the underscore before the error name, this is to prevent an override to default next.js custom error file.

_error.ts

_22
_22
export default function CatchError(fn: Function) {
_22
return async function (req: Request) {
_22
try {
_22
return await fn(req);
_22
} catch (err: any) {
_22
const code = RESPONSE_CODE[err.code as any];
_22
console.log(`😥 Error [${code}]: ${err?.message}`);
_22
console.log(err);
_22
if (err instanceof HttpException) {
_22
return sendResponse.error(err.code, err.message, err.statusCode, err);
_22
}
_22
_22
return sendResponse.error(
_22
RESPONSE_CODE.INTERNAL_SERVER_ERROR,
_22
"INTERNAL SERVER ERROR",
_22
500,
_22
err
_22
);
_22
}
_22
};
_22
}

This error handler is as simple as your Grade 8 math 😅. It an HOF which takes in a function, wraps the function in a try...catch block and returns the function. If an error is thrown from the function, it is caught and handled by the catch() block. Within the catch block, is where we simply send the errors to the client.

Why this Approach? Good question, this approach

  • Get rid of unnecessary try..catch blocks in your controller files.
  • Stop using return new Response(...) every time you need to send an error response to the client.
  • Centralize tracking of all server-thrown errors in one location, opening up the option to utilize sentry for comprehensive error tracking.
  • Enhance the readability and maintainability of your codebase.

Validation Error (Zod) 🧪

Veloz kits using javascript as preferred language uses Zod package for schema validation. This package is used to validate the request body, query params and headers. This package is used in the src/app/api/utils/zodValidation.ts file.

It a reusable utility function which makes it easy to validate request body, query params and headers. It takes in a schema and data to validate, if the validation fails, it throws a VALIDATION_ERROR which is handled by the CatchError() middleware.

All related zod schema validation are stored within the src/app/api/utils/schema_validation.(ts)

zodValidation.ts

_14
export default async function ZodValidation(
_14
schema: AnyZodObject,
_14
body: object,
_14
pathname: string
_14
) {
_14
try {
_14
const { searchParams } = new URL(pathname);
_14
const query = searchParams;
_14
schema.parse(body ?? query);
_14
} catch (error: any) {
_14
const msg = error?.issues[0]?.message as any;
_14
throw new HttpException(RESPONSE_CODE.VALIDATION_ERROR, msg, 400);
_14
}
_14
}

src/app/api/utils/schema_validation.(ts)

_25
import zod from "zod";
_25
_25
// Declare all your api server schema validations here
_25
_25
// registeration schema
_25
export const registerSchema = zod.object({
_25
username: zod
_25
.string({
_25
required_error: "Username is required",
_25
})
_25
.min(3)
_25
.max(50),
_25
email: zod
_25
.string({
_25
required_error: "Email is required",
_25
})
_25
.email(),
_25
password: zod
_25
.string({
_25
required_error: "Password must be at least 6 characters long",
_25
})
_25
.refine((data) => data.length >= 6, {
_25
message: "Password must be at least 6 characters long",
_25
}),
_25
});

Usage: This utility function is meant to be used in the controller files, for eg:


_11
import ZodValidation from "../utils/zodValidation";
_11
import { registerSchema } from "../utils/schema_validation";
_11
_11
export default class AuthController {
_11
// register user (nextauth, jwt)
_11
public async register(req: NextRequest) {
_11
const payload = (await req.json()) as RegisterPayload;
_11
// validate payload
_11
await ZodValidation(registerSchema, payload, req.url);
_11
}
_11
}


Sending Response 📦

How about sending response to client? well we've got you covered, we've created a utility class called sendResponse() which is used to send response to the client, this function can be found in the src/app/api/utils/sendResponse.(ts) file. This class exposes two methods namely error() and success().

  • error() - This method is used to send error response to the client.
  • success() - This method is used to send successfull response to the client.

Usage:

⚠️

Since Nextjs app router expects an error response object to get returned, always make sure to return the response object from the sendResponse() function.


_10
return sendResponse.error(
_10
RESPONSE_CODE.INTERNAL_SERVER_ERROR,
_10
"INTERNAL SERVER ERROR",
_10
500,
_10
err
_10
);

© Veloz 2024