How to create request middlewares in NextJs the clean way

Luca Zani
4 min readSep 25, 2022

Middlewares are an important aspect of every API and they could be useful also in a NextJs project but the official docs don't teach you how to implement them, thankfully you found this article where I’m going to teach you how to add request middlewares to your NextJs application!

The NextJs 12.2 version brought a new concept of middleware. This is a nice concept and you should check it out but it's not what we’re going to talk about in this guide.

How to make your first middleware

Pick up your NextJs project and add a new file called withHello.ts. Place it wherever you want except for the reserved /pages directory; the file path and names we’re going to use don’t have any functional value unlike other stuff in the NextJs framework.

As you may have guessed from the filename, we’re going to work with TypeScript to also address the typing part. You can easily replicate everything with plain JavaScript by removing the type annotations

Here is our middleware code which will simply console.log("Hello World") on every request (Don’t worry, we’re going to explore some real use cases too, this is just to explain the flow).

export const withHello =
(handler: NextApiHandler) => // 1
(req: NextApiRequest, res: NextApiResponse) => { // 2
console.log("Hello World!");
return handler(req, res); // 4
};
  1. The middleware is a function that accepts a handler(and eventually some options, see the Validation Example)
  2. The function returns a closure with req and res params
  3. Inside the body of the closure, we can execute whatever we want while having access to the req and res
  4. Finally, we return the handler along with req and res that can be eventually modified with additional values (See the Auth Example)

To use the middleware simply wrap the handler function of the route you want to apply it to

const handler = async (req: NextApiRequestWithAuth, res: NextApiResponse) => {
...
}
export default withHello(handler)

Here is a basic explanation of how the middleware system works, if you wish to see some real-world examples of what we’ve learned please continue reading

How to make an authentication middleware

One of if not the most common use cases for middleware is to perform authentication on a specific route to protect it from unauthenticated or unauthorized users.

Here is how you can make a basic Authentication middleware with JWT verification

export type CustomNextApiHandler<
TReq extends NextApiRequest = NextApiRequest,
TRes extends NextApiResponse = NextApiResponse
> = (req: TReq, res: TRes) => unknown | Promise<unknown>;

export type NextApiRequestWithAuth = NextApiRequest & {
userId: string;
};

export const withAuth =
(handler: CustomNextApiHandler<NextApiRequestWithAuth>) =>
(req: NextApiRequestWithAuth, res: NextApiResponse) => {
const { authorization } = req.headers;
if (!authorization) {
return res.status(403).json({ message: "Unauthorized" });
} else {
const token = authorization.split(" ")[1];
jwt.verify(token, process.env.JWT_SECRET!, (err, payload) => {
if (err || !payload) {
return res.status(403).json({ message: "Unauthorized" });
}
req.userId = (payload as { uid: string }).uid;
return handler(req, res);
});
}
};

The usage is the same as in the first example. As you can see with this middleware we’re injecting a custom userId prop into the req that can be accessed inside the handler

const handler = async (req: NextApiRequestWithAuth, res: NextApiResponse) => {
return res.status(200).json({ message: "Hello" + req.userId });
}
export default withAuth(handler);

How to make a validation middleware

Another use case for middlewares is to validate input data, in the next example we’re going to see how you can manage to do so along with learning how to pass custom options into our middleware.

In this example we’re using Yup as our schema validation library but you can achieve the same result with whatever library you’re most comfortable with

type WithValidationOptions = {
bodySchema?: yup.AnyObjectSchema;
};

export const withValidation =
(handler: NextApiHandler, options: WithValidationOptions) =>
async (req: NextApiRequest, res: NextApiResponse) => {
try {
await options.bodySchema?.validate(req.body);
} catch (error) {
if (error instanceof ValidationError) {
return res.status(400).json({ message: error.errors[0] });
}
}
return handler(req, res);
};

To use it simply wrap the handler as we’ve done before and as the second parameter you can pass the options. In this particular case pass the validation schema!

...
export default withValidation(handler, {
bodySchema: yup.shape({
refreshToken: yup.string().required(),
}),
});

Thank you for reading this far, I hope you found this article useful, if so consider clapping it, following me for more content, or asking any kind of question in the comments. Bye!

--

--