[Nuxt] Middleware Intro
As we explore the Nuxt ecosystem, another valuable resource we’ll be taking a look at is Nuxt Middleware.
Nuxt provides us with two types of middleware — server and client middleware but before we talk about these types of middleware, let’s understand what a middleware is.
What is Middleware
So, what is Middleware?
To put it simply, Middleware allows us to run some code before a page is rendered. In the case of server Middleware in Nuxt, it is code run on every request before a server route and this is only applicable when using Nuxt 3 server. In the case of route middleware in Nuxt, it is code that runs on the client before navigating to a specific route.
Common examples of code you’d want to run before navigating to a page include things like:
- Checking for authentication.
- Redirection.
- Analytics and so forth.
Route Middleware
Let’s take a look at the first type of middleware in Nuxt, Client/Route middleware. Nuxt middleware can make your Nuxt app more efficient and secure and makes it easy to run custom logic on each route on your app.
You can think of it as the checkpoint right before you enter a venue. Middleware acts as the vigilant bouncer, ready to make sure only people with the proper credentials can enter.
Although the term “middleware” may sound technical and perhaps intimidating… ultimately, it’s just a JavaScript function. Let’s take a look at the fundamental structure of a middleware function.
A middleware function always accepts two arguments from Nuxt — to and from. In other words, we know where the user is going TO, and where they are coming FROM.
These arguments are essentially route objects, which typically include values found in a route data parameter, such as params, query, and path.
Use-case for Route Middleware
So what are some ways to use Nuxt middleware in your applications?
A common use case for middleware is to navigate users to a specific page. To achieve this, we can use the Nuxt middleware global helper method called navigateTo().
Let’s say for example we want to check if our user is an admin, and if they are not, we’ll redirect them to a login page.
The way we’d do this is to check if the to.path is the /admin page. If the user does not have an admin property, we’d redirect them using navigateTo(), which accepts the name of the path we want to redirect the user to. In this case, the login page.
We can also pass in an optional config, where we can define things such as whether or not we want to replace the browser history. By default, this is false, which means the user can still hit the back button. But we can update that, along with setting a redirectCode, and redirect to an external page. For example, if we want to redirect to a URL that lives outside of our app’s routes, we can set external to true. This informs Nuxt that this is a secure path, which we recognize as safe.
How to use Route Middleware
So, how and where do we actually use our route middleware? There are several ways to implement middleware.
Inline Middleware
One method is within a component, as demonstrated in the script section.
📄 adminView.vue
<script setup>
definePageMeta({
middleware: function (to, from) {
if (to.path === '/admin' && user.admin === false) {
return navigateTo('/login')
}})
</script>
We’ll first add the definePageMeta() which is a global Nuxt 3 function that allows us to configure page settings. Then we’ll add the middleware property inside the function. This is what we call inline middleware since it’s added inline into the component.
Named Middleware
Alternatively, we can extract our middleware into its own file that we add to our middleware directory. This allows us to simply add the name of our middleware into an array wherever we need it. This is called named middleware and it can lead to more maintainable and scalable implementation of middleware throughout your projects.
We can go ahead and export a standard javascript function like we did for our inline middleware.
📄 middleware/admin.js
export default function (to, from) {
if (to.path === '/admin' && user.admin === false) {
return navigateTo('/login')
}
}
However, instead of using an anonymous function, which can lead to difficult debugging, we can use Nuxt’s defineNuxtRouteMiddleware
helper method. This method automatically adds types to our function, improving things like autocomplete and making our code easier to
export default defineNuxtRouteMiddleware(to => {
if (to.path === '/admin' && user.admin === false) {
return navigateTo('/login')
}
})
We can then go ahead and update this in our component.
📄 adminView.vue
<script setup>
definePageMeta({
middleware: ['admin'] //this can be a single middleware string or an array
})
</scrpt>
The definePageMeta
function provided by Nuxt automatically checks the middleware directory. This means we don’t need to manually import the middleware. Instead, we simply use the name of the middleware file as a string in the function.
Global Middleware
We could even make middleware Global. Doing this is very straightforward.
We just append the term global onto our middleware’s file name. Now, this middleware is available everywhere without setting it up locally within specific components.
📁 middleware/admin.global.js
export default function (to, from) {
if (to.path === '/admin' && user.admin === false) {
return navigateTo('/login')
}
}
Go ahead and delete the middleware declaration from the adminView and everything should work as expected.
📄 adminView.vue
<script setup>
</scrpt>
Server Middleware
The second type of middleware Nuxt provides exists on the server and it’s know as server middleware. Server Middleware is any code that runs prior to the server code being executed.
This is how the server middleware works:
A request is sent, some middleware code is executed and then the server code returns a response.
How to set up Server Middleware
Since the server middleware functions only in the context of the Nuxt 3 server, it needs to live in the server directory. The next step is to create a middleware directory within this server directory. Now, any file that exists within this middleware directory is treated as a server middleware.
Nuxt middleware is primarily designed for the client and since we’re on the server, we won’t be able to use things like defineNuxtMiddleware
here. Instead, like most things on the server, we will define an event handler.
Let’s look at an example where we implement a server middleware to validate user authentication.
We have a simple login page that sets a cookie with the user ID using the Nuxt 3 composable: useCookie:
📁 pages/login.vue
<script setup lang="ts">
const authCookie = useCookie('user-auth')
function authenticate() {
authCookie.value = '1f38d'
}
</script>
<template>
<h1>Login Page</h1>
<button @click="authenticate">Authenticate</button>
</template>
Inside our middleware, we can use this cookie to validate user authentication. Since we’re on the server, we won’t be able to use the useCookie composable as this only works in the Nuxt context.
As a result, rather than using useCookie
, we’ll need a new function getCookie.
When using getCookie
, it’s important to remember that server code lacks browser context. Therefore, we’ll need to pass in the event
as the first argument, followed by the cookie ID you want to retrieve.
📁 server/middleware/auth.js
export default defineEventHandler(async event => {
const auth = getCookie(event, 'user-auth')
})
When working with server middleware, resuming the intended code operation can be as simple as a return
statement. So we want to validate if the cookie exists. If it does, we’ll return that everything checks out and it’s okay to resume
📁 server/middleware/auth.js
export default defineEventHandler(async event => {
const auth = getCookie(event, 'user-auth')
if (auth) {
return true
}
})
If not, then we need to redirect them to the login page. Since we’re not on the client anymore, functions like navigateTo
are unavailable. So, we’ll need something else to tell the browser to redirect the user. The sendRedirect
function would work great in this case. It accepts three arguments:
- The event we’re redirecting
- Where we want it to go (the path)
- and the redirect code
📁 server/middleware/auth.js
export default defineEventHandler(async event => {
const auth = getCookie(event, 'auth')
if (auth) {
return
} else {
return sendRedirect(event, '/login', 302)
}
})
Based on the way our code works, it’ll always redirect to the login page when auth is false, even if we’re trying to navigate to the login page to get authenticated. To prevent this infinite redirect, we’ll need to determine the user’s current path. To do this we can use the getRequestURL
helper method. What this does is accept an event and return an object of the user’s current path. Once we have this, we can go ahead and add an extra condition to our if
statement like this
📁 server/middleware/auth.js
export default defineEventHandler(async event => {
const auth = getCookie(event, 'auth')
const url = getRequestURL(event)
if (auth || url.pathname === '/login') {
return
} else {
return sendRedirect(event, '/login', 302)
}
})
Now our app should work as expected.