I'm currently moving my site to Next.js from a site that used an Express backend. I'm a huge fan of Express, and one of the things I love about Express is how simple it is to implement middleware.
Middleware is a function that you can attach to routes in order for it to run before the route. In other words, it runs in "the middle." This is useful for things like checking if a user is authenticated or has the proper roles to access a route.
These are features you would want on multiple routes, and middleware makes it easy to write the function in one place and reuse it across multiple routes (Don't Repeat Yourself). But what about Next.js–how do you implement middleware?
Well, you could create a custom Express server to work with Next.js, but you're losing some of the benefits of creating your API the way Next.js intended.
Instead, you can watch how I implement middleware in my Next.js application in the video below, or for those that rather read I have a written explanation below the video–enjoy! 😊
Code: https://github.com/hunterbecton/next-js-middleware
Before: My Express Protect Middleware
In Express I have all my routes and middleware associated with authentication inside an authController.js
file. One of those middleware functions is the protect
function, which you can see in the code below:
This protect function would check to see if the req
had cookies and if the st_accessToken
cookie was present. If so, it would attach the token to the token
variable defined at the top of the function.
If no token was present my application would return an error asking the user to log in, so the user would never reach the final route. If a token was present the function would then proceed to run some code in a try / catch
block.
Inside the try
block the token would be decoded using the jwt.verify
method from the jsonwebtoken
package. This method is passed the token and my application's JWT secret. Once that's decoded my application has access to the user's unique ID on decoded.id
. This user ID is then used to make a call with Mongoose to my MongoDB database: const currentUser = await User.findById(decoded.id);
If no user if found the application will return an error, otherwise the function will attach a user
object on req
based on the currentUser
that came back from MongoDB. Last, unless an error is caught in the catch
block, the function calls next()
, which tells Express to move to the next route handler or middleware.
Now routes or middleware further down the chain will have access to the user object and can use it however they want. For example, my routes for Stripe will now be able to read the Stripe Customer ID from the user object that is attached to req.user
.
This middleware is implemented in Express when I create my routes in the userRoutes.js
file:
Now: My Next.js withProtect Middleware
In Next.js you can create your routes in a folder called api
, which needs to be inside the pages
folder. Then, inside your api
folder you can create all your route handlers, nesting them inside other folders based on how you want your API to be organized. Next.js will handle creating the routes for you, so there's no need to define them like you would in Express.
For example, a logout.js
handler inside pages > api > users > logout can be accessed in development from localhost:3000/api/users/logout
. Pretty neat, right?
However, because Next.js handles the routing for us, we can't just pass a middleware before the route is called when we define routes ourselves in Express. So now let's look at the withProtect middleware code:
Look familiar? That's because it's almost identical to the protect middleware in Express! However, there are some important things to point out. To make them easier to see, check out this code example below where I remove some of the identical code:
You can now see how this withProtect middleware takes the handler in as an argument, then returns a function of (req, res)
. This essentially takes over the handler for now, before it later passes it back into the original handler when handler(req,res)
is returned.
Now with the withProtect middleware complete, it's time to implement it inside the logout route. Refer to the following code:
Inside the logout handler you can see that it has access to req.user
, which is passed on by the withProtect middleware.
So how does the logout handler get the access? Well, if you look at the bottom of the code you will see that I wrapped the exported handler with the withProtect middleware–identical to how you would do higher order components in React: export default withProtect(handler);
.
By doing this, the withProtect middleware will run before the logout handler and give the logout handler the user object on req.user
, unless there is an error and the middleware will return an error.
Chaining Multiple Middlewares in Next.js
What happens if you want to add multiple middleware on a handler? Simple, you just nest it inside the other middlewares!
For example, check out this withRoles middleware:
This middleware takes two arguments: the original handler function and a string of possible roles that can access the route.
Users are assigned the role of user
in my database by default when they're created. This is important because there are some routes, like deleting a user, that I only want users with the role of admin
to access.
This withRoles middleware gets access to the user on req.user
because it's nested inside the withProtect middleware. Although it doesn't make much sense to only allow admins to logout, check out this simplified example of the logout handler:
First, the withProtect middleware runs, either attaching the user object on req.user
or returning an error. Then the withRoles middleware checks to see if the req.user.role matches 'admin'. If it does, the logout handler is then called. Otherwise, the middleware returns a response notifying the user that they do not have the proper permissions.
Nesting middleware like this can look a little weird compared to how you would implement it in Express, but once you get the hang of it the implementation in Next.js is simple.
Found this helpful? Subscribe to my YouTube channel
Top comments (2)
Hey Hunter, good read. I'm really curious how this would work now that NextJS has added support for middleware directly (in v12). Specifically struggling to understand how to do something like withRoles. Ideally I'd want to define something at the route level that indicates which roles are required to access that route, and then the middleware would be able to use that information to guard against that route.
Haven't really found anything that supports this use case. I think I'm just going to have a base middleware that validates that the token, and then use something like your withRoles HOC to validate the roles at the endpoint level.
i am not sure if we can do this "// Grant access to protected route
req.user = currentUser;" in nextjs