Express + NextJS - sample/tutorial integration
Tags: tutorial, nextjs, node, javascript
Published: 2021-06-20 21:41:00

# Context
While NextJS is a wonderful tool in its own right, augmenting it with Express makes for a powerful combo.

One motivation may be simplicity - if you have a project you're trying to prototype and rapidly iterate on. These days, it's common to host the front end separately from the API, but then your project starts off as a distributed system - and you have to deal with [extra complexity](https://aws.amazon.com/builders-library/challenges-with-distributed-systems/) up front.

Some other use cases where it makes sense to do this type of combination:
- Enabling an existing Express API server to serve some front end with React/SSR
- Run some express middleware and fetch standard data for NextJS pages before they are served
- Adding custom logic to NextJS routing
- Adding WebSocket functionality (e.g. for a chat app)

This type of setup is documented in NextJS itself: https://nextjs.org/docs/advanced-features/custom-server

In the standard example, they use Node's `http` package; we'll use Express to take advantage of its middleware and routing capabilities.

# Source code
I've provided an example barebones integration - as a github template - at https://github.com/alexey-dc/nextjs_express_template

I also wrote an article on how to make this type of setup production-ready with PM2: https://dev.to/alexeydc/pm2-express-nextjs-with-github-source-zero-downtime-deploys-n71

Using that setup, I've hosted the demo at <a target="_blank" href="https://nextjs-express.alexey-dc.com/">https://nextjs-express.alexey-dc.com/</a> (it's just the template run on a public URL). The main difference with the code explained here is the PM2 configuration, which I use for zero downtime deploys.

# The integration
Let's take a look at some highlights of this NextJS+Express setup.

The main entry point is `index.js`, which sets up the environment and delegates spinning up the server:

```javascript
require("dotenv").config()
const Server = require("./app/server")
const begin = async () => {
  await new Server(process.env.EXPRESS_PORT).start()
  console.log(`Server running in --- ${process.env.NODE_ENV} --- on port ${process.env.EXPRESS_PORT}`)
}
begin()
```

Note that I'm relying on `dotenv` to load environment variables - e.g. `EXPRESS_PORT`, `NODE_ENV`, and a few others. You can see the full list of necessary environment variables in the README in the github repository.


In the server, both `nextjs` and `express` are initialzed, along with express midleware and a custom NextjsExpressRouter I built to take the routing over from NextJS into our own hands:


```javascript
  this.express = express()
  this.next = next({ dev: process.env.NODE_ENV !== 'production' })
  this.middleware = new Middleware(this.express)
  this.router = new NextjsExpressRouter(this.express, this.next)
```

The middleware I included is quite barebones, but serves as an example of what you might have in a real application:

```javascript
  this.express.use(bodyParser.json());
  this.express.use(bodyParser.urlencoded({ extended: false }));
  this.express.use(favicon(path.join(__dirname, '..', 'public', 'favicon.png')));
```

The NextjsExpressRouter is really the heart of the integration. Let's take a closer look.

# NextjsExpressRouter
The idea is to allow GET routes for pages to co-exist with API HTTP routes:

```javascript
class NextjsExpressRouter {
  constructor(express, next) {
    this.express = express
    this.next = next
  }

  async init() {
    this.initApi()
    this.initPages()
    this.initErrors()
  }

  initApi() {
    return (new (require("./routes/api.js"))(this.express)).init()
  }

  initPages() {
    return (new (require("./routes/pages.js"))(this.express, this.next)).init()
  }
// ...
/* Some standard error handling is also included in the repo code */
}
```

I split out the API from the page routes into separate files, and I find that as the codebase grows, it helps to impose some sort of grouping or hierarchy on endpoints. Pages and API calls seem like the most basic organization. Note I made the `init()` function async. In this case we don't need to run any I/O operations or other async initialization, but in the general case we might want to.

For my larger projects, the API typically itself has several sub-groups, and sometimes pages do as well. In this sample project that has very few routes, the API and pages are a flat list of routes:

```javascript
const data = require("../data/integer_memory_store.js")

class Api {
  constructor(express) {
    this.express = express
  }

  init() {
    this.express.get("/api/get", (req, res) => {
      res.send({  i: data.value })
    })

    this.express.post("/api/increment", (req, res) => {
      data.incr()
      res.send({ i: data.value })
    })
  }
}
```

Obviously this is just a minimal sample API - all it does is allows you to read and increment an integer stored in memory on the server.


Here's how the NextJS page routes are defined:
```javascript
const data = require("../data/integer_memory_store.js")

class Pages {
  constructor(express, next) {
    this.express = express
    this.next = next
  }

  init() {
    this.initCustomPages()
    this.initDefaultPages()
  }

  initCustomPages() {
    /* With a monolith api+frontend, it's possible to serve pages with preloaded data */
    this.express.get('/preload_data', (req, res) => {
      res.pageParams = {
        value: data.value
      }
      return this.next.render(req, res, `/preload_data`)
    })

    /* Special-purpose routing example */
    this.express.get('/large_or_small/:special_value', (req, res) => {
      const intValue = parseInt(req.params.special_value)
      if(isNaN(intValue)) {
        return this.next.render(req, res, `/invalid_value`, req.query)
      }
      if(intValue < 5) {
        return this.next.render(req, res, `/special_small`, req.query)
      } else {
        return this.next.render(req, res, `/special_large`, req.query)
      }
    })
  }

  initDefaultPages() {
    this.express.get('/', (req, res) => {
      return this.next.render(req, res, `/main`, req.query)
    })

    this.express.get('*', (req, res) => {
      return this.next.render(req, res, `${req.path}`, req.query)
    })
  }
}

module.exports = Pages
```

The page routes showcase setting up a root `/` path, and a fallback `*` path - if we're not able to match the GET request, we default to what NextJS's standard behavior is: rendering pages by filename from the `/pages` directory. This allows for a gentle extension of NextJS's built-in capabilities.

There are 2 examples for custom routing.

In the first example, we pre-load some data, and bake it into the page before serving it to the user. This may be useful to avoid an extra HTTP roundtrip after the page renders, and is difficult to pull off w/o a monolithic API+frontend setup as presented here.

In the second example, we render different variants of a page depending on an integer value in the route - or an error, if the input is invalid. Perhaps a real application may fetch user data, and render it differently depending on some condition (e.g. the viewer's relationship with them) - and render an error if the user is not found.

# Using the template
I licensed the code under MIT - which means you are free to use the template in closed-source and commercial products, and make any modifications you want. Please attribute/give credit if you are able to!

It's also a template on github, which means you can just click a button and start a new repo based on https://github.com/alexey-dc/nextjs_express_template

![Screen Shot 2021-06-20 at 5.36.59 PM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6f4cy78rn2yx8k7good6.png) 
 
## Running
The instructions for running are in the [github repo](https://github.com/alexey-dc/nextjs_express_template#running). 

## Iterating
You'll probably want to delete the sample custom endpoint and associated pages I provided - and start replacing them with your own!

I included a sample organization for pages as well - the page roots are in `pages` as nextjs mandates, but all the reusable `jsx` is in `views` - for the demo, I was using a common layout for pages, and the `Layout` component is housed in `views`.