Using Middleware

Using middleware in API route #

First, let me explain what is middleware and why we need middleware layer in Next.js.

In Express/Koa, we can use middlewares to access the request object and response object in a request-response cycle. A middleware is a function that take three params: req, res and next:

function exampleMiddleware(req, res, next) {
  if (/** ...*/) {
    req.foo = 'bar'
  	next()
  } else {
    res.statusCode = 403
    res.send('forbiddon')
  }
}

Since this middleware pattern is first introduced by a Node.js framework connect, we always call these middlewares connect-like middleware.

In a middleware, we can both:

  • Inject some properties on the req object, which can be used on the next middleware or route hadler (controller).
  • Suspend the request by not calling next() and modify the response (such as status code, response body).

This is very usable for reusing logic between routes. Imagine we need to get current signed in user’s info in some routes, we can write this logic into a middleware, then apply it before the routes that need to get user’s info. Here is an example using middleware in Express:

function authMiddleware(req, res, next) {
  // suppose we saved a `token` in cookies after user signing in
  if (req.cookies.token) {
    const user = getUserByToken(req.cookies.token)
    req.user = user
    next()
  } else {
    // user not signed in
    res.statusCode = 403
    res.send('please sign in first')
  }
}

// route that doesn't need user's information
app.get('/', (req, res) => {
  res.send('hello world')
})

// route that need user's information
app.get('/profile', authMiddleware, (req, res) => {
  res.send(`welcome! ${req.user.name}`)
})

Middleware makes code very reusable. But in Next.js, there isn’t a middleware layer like it. Imagine we need to do the same thing in some of Next.js API route handlers:

// pages/api/example.ts

function auth(req, res) {
  if (req.cookies.token) {
    const user = getUserByToken(req.cookies.token)
    return user
  } else {
    // user not signed in
    res.status(403)
    res.send('please sign in first')
  }
}

export default (req, res) => {
  if (req.method === 'GET') {
    res.send('hello')
  } else if (req.method === 'POST') {
    const user = auth(req, res)
    console.log('do other things')
	  res.send(`welcome! ${user.name}`)
  }
}

It seems good but what if const user = auth(req, res) return nothing? res.status(403) would be executed but it doesn’t suspend the whole request, console.log('do other things') is gonna be executed unexpectedly.

Introducing next-connect #

To make use of connect-like middlewares in Next.js, we can use an amazing library —— next-connect.

Install next-connect:

$ yarn add next-connect

Now, let’s see how we can use next-connect in a Next.js api route handler:

// pages/api/example.ts

import nc from 'next-connect'

function authMiddleware(req, res, next) {
  // just make a 403 response
  res.status(403)
  res.send('please sign in first')
}

// First, we use `nc()` to create a api route handler`
const handler = nc()
  .get((req, res) => {
    res.send('hello')
  })
  .post(authMiddleware, (req,res) => {
    res.send('hello')
  })

export default handler

In the above code example, you can see we can now use connect-like middleware in api route, just like how we can do in Express. The authMiddleware change the response status code to 403 and send request body, but didn’t call next(), so the res.send('hello') code in the POST handler wouldn’t be executed.

Another greate benefit of using next-connect is we can use .get(), .post(), put(), etc, rather than using if (req.method === XXX). It makes code more readable.

Another example is to enable CORS in an API route. We can just use the Express cors middleware:

Install cors:

$ yarn add cors
// pages/api/example.ts

import nc from 'next-connect'
+ import * as cors from 'cors'

const corsOptions = {
  origin: 'http://example.com',
  optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}

// First, we use `nc()` to create a api route handler`
const handler = nc()
+	.use(cors(corsOptions))
  .get((req, res) => {
    res.send('hello')
  })
  .post(authMiddleware, (req,res) => {
    res.send('hello')
  })

export default handler