Core concepts
RESTful API
This doc will show you how to set up a RESTful API with next-api-handler with CRUD example.
CRUD example
Imagine you have a User
service that provides CRUD operations for users. next-api-handler can accept an async function as the handler, so you can use async/await
to handle the request.
import { RouterBuilder } from 'next-api-handler';
import { createUser } from '@/services/user';
const router = new RouterBuilder();
router
.get(() => [{ id: 1, name: 'John Doe' }])
.post(async (req) => createUser(req.body));
export default router.build();
Which is equivalent to the following code and return the same json
response.
import { createUser } from '@/services/user';
export default async function handler(req, res) {
if (req.method === 'POST') {
const user = await createUser(req.body);
res.status(200).json({ success: true, data: user });
} else if (req.method === 'GET') {
res.status(200).json({ success: true, data: [{ name: 'John Doe' }] });
} else {
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).json({
success: false,
message: `Method ${req.method} Not Allowed`,
});
}
}
Type safety
If you are using TypeScript
, we can also enforce the response type by using generics.
import { RouterBuilder } from 'next-api-handler';
import { createUser } from '@/services/user';
type User = {
id: number;
name: string;
};
const router = new RouterBuilder();
router
.get<User[]>(() => [{ id: 1, name: 'John Doe' }])
.post<User>(async (req) => createUser(req.body));
export default router.build();
The following will show TypeScript
error.
/**
* Argument of type '() => { id: number; }' is not assignable to parameter of type 'NextApiHandlerWithMiddleware<User, TypedObject>'.
* Type '{ id: number; }' is not assignable to type 'void | User | Promise<User>'.
* Property 'name' is missing in type '{ id: number; }' but required in type 'User'.ts(2345)
* user.ts(3, 3): 'name' is declared here.
* */
router.put<User>(() => ({ id: 1 }));
HTTP exceptions and error handling
next-api-handler provides a set of HTTP exceptions that can be used to throw errors in the handler function. The error will be caught by next-api-handler and return a json
response with the status code and message.
Built-in exceptions
import { RouterBuilder, BadRequestException } from 'next-api-handler';
const router = new RouterBuilder();
router.get(() => {
throw new BadRequestException('Something went wrong');
});
export default router.build();
Which is equivalent to the following code and return the same json
response.
export default async function handler(req, res) {
if (req.method === 'GET') {
res.status(400).json({
success: false,
message:
process.env.NODE_ENV === 'production'
? 'Bad Request'
: 'something went wrong ',
});
} else {
res.setHeader('Allow', ['GET']);
res.status(405).json({
success: false,
message: `Method ${req.method} Not Allowed`,
});
}
}
Please refer to the API reference for more detailed configuration of the router.
Custom exceptions
You can also create your own exceptions by extending or using the HttpException
class.
import { RouterBuilder, HttpException } from 'next-api-handler';
class CustomException extends HttpException {
constructor(message) {
super(400, message);
}
}
const router = new RouterBuilder();
router.get(() => {
throw new CustomException('Something went wrong');
});
export default router.build();
The following will return the same json
response.
import { RouterBuilder, HttpException } from 'next-api-handler';
const router = new RouterBuilder();
router.get(() => {
throw new HttpException(400, 'Something went wrong');
});
export default router.build();
Uncaught exceptions
If an non-HttpException
is caught by next-api-handler, it will be handled by the default error handler. The default error handler will return a json
response with the status code 500
and the error message.
import { RouterBuilder } from 'next-api-handler';
const router = new RouterBuilder();
router.get(() => {
throw new Error('Something went wrong');
});
export default router.build();
which is equivalent to the following code and return the same json
response.
export default async function handler(req, res) {
if (req.method === 'GET') {
res.status(500).json({
success: false,
message:
process.env.NODE_ENV === 'production'
? 'Internal Server Error'
: 'Something went wrong',
});
} else {
res.setHeader('Allow', ['GET']);
res.status(405).json({
success: false,
message: `Method ${req.method} Not Allowed`,
});
}
}
This also applies to rejected promises.
import { RouterBuilder } from 'next-api-handler';
const router = new RouterBuilder();
router.get(() => {
return Promise.reject('Something went wrong');
});
export default router.build();
Custom exception handler
You can also create your own exception handler by creating a function that accepts NextApiRequest
, NextApiResponse
and Error
as parameters fulfilling the ApiErrorHandler
type.
export type ApiErrorHandler = (
req: NextApiRequest,
res: NextApiResponse<ErrorApiResponse>,
error: Error
) => void;
Then pass the function to the RouterBuilder
constructor.
import { RouterBuilder } from 'next-api-handler';
const router = new RouterBuilder({
error: (req, res, error) => {
// Your custom error handler, remember adding res.json() to return the response
},
});
export default router.build();
The following will generate the default error handler based on RouterBuilder constructor options. showMessage
field.
By default, showMessage
is set to false
for production environment and true
otherwise.
export const makeErrorHandler =
(showMessage: boolean): ApiErrorHandler =>
(_req, res, error): void => {
if (error instanceof HttpException) {
return res.status(error.status).json({
success: false,
message: showMessage ? error.message : error.defaultMessage,
});
}
res.status(500).json({
success: false,
message: showMessage ? error.message : 'Internal Server Error',
});
};