lightweight next.js api handler wrapper, portable & configurable for serverless environment
next-api-handler contains helper functions and generic types to create next.js API routes with strong type declaration and type-checking.
next-api-handler will help in the following 3 aspects
- sharable & express-like apis with middleware to build RESTful api route
- simple response handler to transform response and error/exception in a predictable way
- friendly generic types to build type-safe API interface shared between client & server
TL;DR
The following snippet will create /api/user
following RESTful standard
// in new /pages/api/user.ts
import { RouterBuilder } from 'next-api-handler';
const router = new RouterBuilder();
router.post(async (req) => createUser(req.query.id));
router.get<{ name: string }>(() => ({ name: 'John Doe' }));
return router.build();
Currently only tested with node.js v14+
with next.js v10+
Feel free to submit if you have issues regard to runtime environments
npm install next-api-handler
# or using yarn
yarn add next-api-handler
We can use express.js
router-like builder
// in new /pages/api/user.js
import { RouterBuilder } from 'next-api-handler';
const router = new RouterBuilder();
router.post((req) => createUser(req.query.id));
router.get(() => ({ name: 'John Doe' }));
// or with async/await
// router.post(async (req) => {
// const user = await createUser(req.query.id);
// return user;
// });
return router.build();
Which is equivalent to the following next.js API routes
// in old /pages/api/user.js
export default async function handler(req, res) {
if (req.method === 'POST') {
const user = await createUser(req.query.id); // some async function/services
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`,
});
}
}
Please see more examples in example folder, can compare /pages/api/before/*
& /pages/api/after/*
Please see examples in before/crud/[id].ts and after/crud/[id].ts
There are some common built-in HTTP exceptions to shorten writing up error response, this can get particularly useful when writing up complex controllers
import { RouterBuilder, BadRequestException } from 'next-api-handler';
const router = new RouterBuilder();
router.get(() => throw new BadRequestException('something went wrong'));
export default router.build();
is equivalent to do
export default function handler(_req, res) {
return res.status(400).json({
success: false,
message:
process.env.NODE_ENV === 'production'
? 'Bad Request'
: 'something went wrong ',
});
}
We can disable process.env.NODE_ENV
check by passing showMessage=true
when creating the router
const router = new RouterBuilder({ showMessage: true });
If there's a piece of shared logic before handling the request, we can create middleware with typescript support
import { UnauthorizedException, NotFoundException } from 'next-api-handler';
import type { NextApiRequest, NextApiResponse } from 'next';
const authMiddleware = async (req: NextApiRequest, res: NextApiResponse) => {
const apiKey = req.cookies['Authorization'];
const isValid = await verify(apiKey); // some async service to check apiKey
if (!isValid) {
throw new UnauthorizedException();
}
const user = await getUserInfo(apiKey); // another async service to fetch user
if (!user) {
throw new NotFoundException();
}
return { user };
};
then we can reuse this user
value in all other routes white enforcing type-checking
import { RouterBuilder, NextApiRequestWithMiddleware } from 'next-api-handler';
// some type returned from getUserInfo
type User = {
id: string;
};
const router = new RouterBuilder();
// or router.inject(authMiddleware) if the order of adding middleware does not matter
router.use<User>(authMiddleware);
// all middleware values will stay in `req.middleware`,
router.get<string, User>(
(req: NextApiRequestWithMiddleware<User>) => req.middleware.user.id
);
return router.build();
This package is carefully designed for TypesScript users to correctly enforce type definitions
For server side, we can declare return types in any of route handler
router.get<{ name: string }>(() => ({ name: 'John Doe' }));
We can share the response type for client side to reuse, e.g. here as axios
// export type ApiResponse<T = unknown> = SuccessApiResponse<T> | ErrorApiResponse;
// export type SuccessApiResponse<T> = { success: true; data: T };
// export type ErrorApiResponse = { success: false; message: string };
import axios from 'axios';
import type { SuccessApiResponse } from 'next-api-handler';
type User = { name: string };
const result = await axios.get<SuccessApiResponse<User>>('/api/...');
or with native fetch
provided by next.js, it is easy to determine whether it is a successful api response
type User = { name: string };
const fetchLocalUser = async (args): Promise<User> => {
const response = await fetch('/api/...', args);
const result = (await response.json()) as ApiResponse<User>;
if (!result?.success) {
throw new Error(result.message);
}
// Here ts should be able to figure out it is SuccessApiResponse
return result.data;
};
As next-api-handler
provides a general interface to generate next.js
handler, it will be easy to work with other libraries like following:
import { RouterBuilder } from 'next-api-handler'
const router = new RouterBuilder()
router.get( ... )
router.post( ... )
// here handler has type 'NextApiHandler' exposed from 'next'
const handler = router.build()
// ... work with wrapper or other plugins
A quick example with iron-session:
// pages/api/user.ts
import { withIronSessionApiRoute } from 'iron-session/next';
import { RouterBuilder } from 'next-api-handler';
const router = new RouterBuilder();
const handler = router.get((req) => req.session.user).build();
export default withIronSessionApiRoute(handler, {
cookieName: 'myapp_cookiename',
password: 'complex_password_at_least_32_characters_long',
// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
cookieOptions: {
secure: process.env.NODE_ENV === 'production',
},
});
- Full API: please refer to Github Page
- Tests:
- unit tests powered by CircleCI
- coverage check powered by codecov
- end-to-end tests powered by Cypress
- vulnerability check by Snyk
- Deployment: A demo website deployed on Vercel with documentation site powered by Github Page