Next.js 13 Middleware for Authentication and Error Handling on API Routes
Tutorial built with Next.js 13.2.4
This is a quick post to show how to add authentication and error handling middleware to all API routes of a Next.js 13 application.
The code snippets in this post are taken from a Next.js + MongoDB auth tutorial I posted recently, the full project and documentation is available at Next.js 13 + MongoDB - User Registration and Login Tutorial with Example App.
API Routes in Next.js
In Next.js the /pages/api
folder contains all API endpoints which are routed based on file name, for example the file /pages/api/users/index.js
is automatically mapped to the route /api/users
. An API route handler exports a default function that is passed the HTTP request (req
) and response (res
) objects.
For more info on Next.js API routes see https://nextjs.org/docs/api-routes/introduction.
Example Next.js API Route Handler
Here's a basic example of an API route handler that returns a list of users to HTTP GET
requests.
You can see that the handler()
function is passed the req
and res
objects for accessing details of the request and setting properties of the response.
import { usersRepo } from 'helpers/api';
export default handler;
function handler(req, res) {
switch (req.method) {
case 'GET':
return getUsers();
default:
return res.status(405).end(`Method ${req.method} Not Allowed`)
}
function getUsers() {
const users = usersRepo.getAll();
return res.status(200).json(users);
}
}
Next.js API Handler
To add support for middleware I created the below apiHandler()
wrapper function, it accepts an API handler
object with a method for each supported HTTP method (e.g. get()
, post()
, put()
, delete()
etc). If a request is received for an unsupported HTTP method a 405 Method Not Allowed
response is returned.
It enables adding Next.js middleware globally to API routes by executing middleware functions (e.g. jwtMiddleware(req, res)
) before the route handler for the HTTP method (handler[method](req, res)
).
Errors are also handled globally by wrapping the middleware functions and handler in a try/catch
block and calling the errorHandler(err, res)
on exception.
import { errorHandler, jwtMiddleware } from 'helpers/api';
export { apiHandler };
function apiHandler(handler) {
return async (req, res) => {
const method = req.method.toLowerCase();
// check handler supports HTTP method
if (!handler[method])
return res.status(405).end(`Method ${req.method} Not Allowed`);
try {
// global middleware
await jwtMiddleware(req, res);
// route handler
await handler[method](req, res);
} catch (err) {
// global error handler
errorHandler(err, res);
}
}
}
Users [id]
API Route Handler
A dynamic API route handler created with the apiHandler() function, it handles HTTP requests with any value as the [id]
parameter (i.e. /api/users/*
). The user id
parameter is attached by Next.js to the req.query
object which is accessible to the route handler.
The route handler supports HTTP GET
, PUT
and DELETE
requests by passing an object with those method names to the apiHandler()
.
import { apiHandler, usersRepo } from 'helpers/api';
export default apiHandler({
get: getById,
put: update,
delete: _delete
});
async function getById(req, res) {
const user = await usersRepo.getById(req.query.id);
if (!user) throw 'User Not Found';
return res.status(200).json(user);
}
async function update(req, res) {
await usersRepo.update(req.query.id, req.body);
return res.status(200).json({});
}
async function _delete(req, res) {
await usersRepo.delete(req.query.id);
return res.status(200).json({});
}
Next.js JWT Auth Middleware
The JWT authentication middleware uses the express-jwt
library to validate JWT tokens in requests sent to protected API routes, if the token is invalid an error is thrown which causes the global error handler to return a 401 Unauthorized
response. The JWT middleware is added to the Next.js request pipeline in the API handler wrapper function.
The register and authenticate routes are made public by passing them to the unless()
method of the express-jwt library. For more info on express-jwt see https://www.npmjs.com/package/express-jwt.
import { expressjwt } from 'express-jwt';
import util from 'util';
import getConfig from 'next/config';
const { serverRuntimeConfig } = getConfig();
export { jwtMiddleware };
function jwtMiddleware(req, res) {
const middleware = expressjwt({ secret: serverRuntimeConfig.secret, algorithms: ['HS256'] }).unless({
path: [
// public routes that don't require authentication
'/api/users/register',
'/api/users/authenticate'
]
});
return util.promisify(middleware)(req, res);
}
Next.js Global Error Handler
The global error handler is used catch all errors and remove the need for duplicated error handling code throughout the Next.js API. It's added to the request pipeline in the API handler wrapper function.
By convention errors of type 'string'
are treated as custom (app specific) errors, this simplifies the code for throwing custom errors since only a string needs to be thrown (e.g. throw 'Username or password is incorrect'
), if a custom error ends with the words 'not found'
a 404
response code is returned, otherwise a standard 400
error response is returned.
If the error is an object with the name 'UnauthorizedError'
it means JWT token validation has failed so a HTTP 401
unauthorized response code is returned with the message 'Invalid Token'
.
All other (unhandled) exceptions are logged to the console and return a 500
server error response code.
export { errorHandler };
function errorHandler(err, res) {
if (typeof (err) === 'string') {
// custom application error
const is404 = err.toLowerCase().endsWith('not found');
const statusCode = is404 ? 404 : 400;
return res.status(statusCode).json({ message: err });
}
if (err.name === 'UnauthorizedError') {
// jwt authentication error
return res.status(401).json({ message: 'Invalid Token' });
}
// default to 500 server error
console.error(err);
return res.status(500).json({ message: err.message });
}
Need Some NextJS Help?
Search fiverr for freelance NextJS developers.
Follow me for updates
When I'm not coding...
Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!