How I migrate from Express.js to Next.js API Routes
Tags: javascript, mongodb, nextjs, express
Published: 2019-08-14 03:02:03

This if a follow up on my previous post [Explore Next.js 9 API Routes](https://www.hoangvvo.com/blog/explore-next-js-9-api-routes/ "Explore Next.js 9 API Routes"). 

After much consideration, I decided to ditch [Express.js](https://github.com/expressjs/express "Express.js") and moved into API Routes. At this point, my project was still simple - I did not have much code. I thought it was better to make the move now before the project get complicated.

## Migrating to Next.js API Routes

In order to use the new API Routes, I need to update my Next.js module to v9 by running:
`npm i next@latest react@latest react-dom@latest`. This updates Next.js along with React.js to the latest versions.

Although this is a major update, I did not find any breaking changes that affect me in particular. However, if there was any for you, there is this [upgrade guide](https://github.com/zeit/next.js/blob/canary/UPGRADING.md "Next.js 9 Upgrade guide") to help you resolve any problems.

### Rewrite codebase - more like, a lot of copypastes

#### Express.js to Next 9 API Routes

In my current express.js Server, to access an endpoint at `/api/authenticate`, my code in `/server/components/account/accountController.js` is:

```javascript
// accountController.js
const express = require('express');

const User = require('../../api/models/userModel');

// In server.js, I called app.use('/api', AccountController);
const AccountController = express.Router();

AccountController.post("/authenticate", (req, res) => {
  const { email, password } = req.body;
  User.findByCredentials(email, password)
    .then(user => user.generateSessionId())
    .then(sessionId => {
      const { name } = user;
      res
        .cookie("sessionId", sessionId, { httpOnly: true, secure: true })
        .send(`welcome my homie, ${name}`);
    })
    .catch(e => {
      // reject due to wrong email or password
      res.status(401).send("who are u, i dun know u, go away");
    });
});
module.exports = AccountController;
```

You can see how I made use of `req` and `res`. Let's look into the Next.js 9 API Routes' [way](https://nextjs.org/docs#api-routes "Next.js 9 API Routes Documentation"):

```javascript
export default function handle(req, res) {
  res.end('Hello World');
}
```

The handle function has the same syntax: it takes the same `req` and `res`. Better yet, Next.js 9's API Routes implements the similar Express.js's `Middlewares`, including parser `req.body` and helper function `res.status` and `res.send`. This means I do not have to make lots of changes.

####  //  FIXME: No __res.cookie__ in Next.js API Routes

It appears that there is no `res.cookie` helper function in Next.js 9 API Routes. I need to rewrite the function, falling back to `http.ServerResponse`'s [setHeader](https://nodejs.org/api/http.html#http_response_setheader_name_value "response.setHeader method") (Because `NextApiResponse` extends `http.ServerResponse`):
`res.cookie("sessionId", sessionId, { httpOnly: true, secure: true })` becomes ``` res.setHeader('Set-Cookie', `sessionId=${sessionId}; HttpOnly; Secure`) ```.

I have created a feature request on [zeit/next.js](https://github.com/zeit/next.js/issues/7923 "Feature request res.cookies") for adding `res.cookie`. I hope they will add it. For now, I have to stick with `res.setHeader`.

####  //  TODO: Creating API Routes version of `/api/authenticate`

I created `pages/api/authenticate.js`.

```javascript
// authenticate.js
export default (req, res) => {
  const { email, password } = req.body;
  User.findByCredentials(email, password)
    .then(user => user.generateSessionId())
    .then(sessionId => {
      const { name } = user;
      res
        .setHeader("Set-Cookie", `sessionId=${sessionId}; HttpOnly; Secure`)
        .send(`welcome my homie, ${name}`);
    })
    .catch(e => {
      // reject due to wrong email or password
      res.status(401).send("who are u, i dun know u, go away");
    });
};
```

Perfect, that was how I transformed my code from __Express.js__ to __Next.js API Routes__: Just copypaste and make small touches. By doing so, I just ditched __Express Router__, making the code so much cleaner. I went and did the same thing for every single API endpoint.

### Uh, oh. Where is our database?

Back at the Express.js version, my `npm start` runs this `server.js` script:

```javascript
const express = require("express");
const mongoose = require("mongoose");
const AccountController = require("./components/account/accountController");
const app = express();
mongoose.connect(process.env.MONGODB_URI, {
  useNewUrlParser: true,
  useFindAndModify: false,
  useCreateIndex: true
});
app.use("/api", AccountController);
app.listen(process.env.PORT);
```
*(I removed Next.js custom server integration for the sake of simplicity)*

Notice that `mongoose.connect()` is how I connected to the database. Routing to `/api/authenticate` was then handled by `app.use("/api", AccountController);`.

Let's take a look at this diagram I draw:

![Express.js and Next.js No MongoDB connection](//images.ctfassets.net/rgbpfpd0hrww/5cPDaQBVY8lvvGYEEB1USw/0bd76f756be6e0dc2fc8fc9d67e97e8d/Express.js_and_Next.js_No_MongoDB_connection.jpg "There is no MongoDB connection in the Next.js version")
*There is no MongoDB connection in the Next.js version*

As you can see, in the Express.js version, the server stays running and maintains the connection. However, in the Next.js version, the server does not have a starting point where it initializes the connection.

How about adding `mongoose.connect()` on __every single endpoints__ (every single `.js` under `/pages/api`. Well, that's not quite the case.

Imagine each time an API Route is hit, it calls `mongoose.connect()`. Therefore, multiple `mongoose.connect()`will be called. However, you can only call `mongoose.connect()` once. Else, you will get this error: 

```
MongooseError: You can not 'mongoose.connect()' multiple times while connected
```

####  //  TODO: Maintain only one Mongoose connection

There must be a way to check if there is a mongoose connection. We only attempt to connect if there isn't one.

This is my approach:

```javascript
// db.js
import mongoose from 'mongoose';

export default async () => {
  if (mongoose.connections[0].readyState) return;
  // Using new database connection
  await mongoose.connect(process.env.MONGODB_URI, {
    useNewUrlParser: true,
    useFindAndModify: false,
    useCreateIndex: true,
  });
};
```
*Edit: Update the correct way to do it*

After successfully connecting to MongoDB, `mongoose.connections[0].readyState` will be **1** (true). Next time when the function is called, it will simply return.

What is left to do is to import the function from db.js in every API endpoint.

```javascript
// authenticate.js
import connectToDb from '../../../api/db';

export default async (req, res) => {
  
  await connectToDb();
  
  const { email, password } = req.body;
  User.findByCredentials(email, password)
    .then(user => user.generateSessionId())
    .then(sessionId => {
      const { name } = user;
      res
        .setHeader("Set-Cookie", `sessionId=${sessionId}; HttpOnly; Secure`)
        .send(`welcome my homie, ${name}`);
    })
    .catch(e => {
      // reject due to wrong email or password
      res.status(401).send("who are u, i dun know u, go away");
    });
};
```

I made the handler an `async` function so that I can use the keyword __await__ on `connectToDb()`. By having the __await__ keyword, we are making sure that `connectToDB()` is completed before anything else.

__That's it!__

####  The alternative way: Using middleware

A "middleware" can be achieved by wrapping the handler function.

Create a `dbMiddleware.js`:

```javascript
import mongoose from 'mongoose';

const connectDb = handler => async (req, res) => {
  if (mongoose.connections[0].readyState) return handler(req, res);
  // Using new database connection
  await mongoose.connect(process.env.MONGODB_URI, {
    useNewUrlParser: true,
    useFindAndModify: false,
    useCreateIndex: true,
  })
  return handler(req, res);
}

export default connectDb;
```

After that in my API functions, I wrap the handler function.

```javascript
import connectDb from '../../../api/middlewares/dbMiddleware.js';
const handler = (req, res) => { 
  const { email, password } = req.body;
  User.findByCredentials(email, password)
    .then(user => user.generateSessionId())
    .then(sessionId => {
      const { name } = user;
      res
        .setHeader("Set-Cookie", `sessionId=${sessionId}; HttpOnly; Secure`)
        .send(`welcome my homie, ${name}`);
    })
    .catch(e => {
      // reject due to wrong email or password
      res.status(401).send("who are u, i dun know u, go away");
    });
};
export default connectDb(handler);
```

Learn more about it in [this post](http://localhost:8000/blog/middlewares-in-next-js-without-custom-server/).

####  TODO: Be consistent when import and export

When using Express.js, I did not implement [Babel](https://babeljs.io/ "Babel") and could not use `ES6 Import / Export`.

When I started to using API Routes (which includes __Babel__), I changed part of the codebase to use __ES6 Import__ or __ES6 Export__. However, several functions still used `module.exports`. This caused an issue that I mention below. (see `FIXME: ... is not a function`).

Therefore, be consistent. I recommend using __ES6 import/export__ for the whole codebase.

### Miscellaneous issues

#### //  FIXME: Blank page with no error

*Note: This particular issue I had below was not originated from Next.js. You can skip it!*

One of the problems I got was that when I ran `next dev`, the terminal shows `build page: /`, `compiling...` then `compiled successfully`. However, when I visited `http://localhost/`, I saw an empty page with the tab's status bar showing loading indication.

![I migrate to NextJS API Routes - Page keeps loading while showing no error](//images.ctfassets.net/rgbpfpd0hrww/20WKmSh4gGRuNBun3YHap3/466c4a744265d85c15dd25b923a44cee/I_migrate_to_NextJS_API_Routes_-_Page_keeps_loading_while_showing_no_error.jpg "Page keeps loading forerver")

When I looked at the Network tab, I saw `GET localhost:3000/` kept on running with no response. (no Status, Response header, payload).

What so annoying about this issue was that there was no __500 internal server error__ or any red error texts in Console.

I looked through my code and checked all the syntax. Everything looked fine. I mean I just copied pasted a working version of my code to the new format. If there was an error in my code, it should had happened before I made the migration.

Luckily, when I attempted to run `next build`, I saw the error:

![I migrate to NextJS API Routes - Build error ENOENT node-sass vendor](//images.ctfassets.net/rgbpfpd0hrww/42NOuYZv1ktbq0hnKnMNSn/98de0004bdaf1c612670077ad5f0f865/I_migrate_to_NextJS_API_Routes_-_Build_error_ENOENT_node-sass_vendor.jpg)
*I found out only when doing my `next build`*

What was node-sass doing? It was totally irrelevant. Then, I thought of that stupid IT joke "Have You Tried Turning It Off And On Again?". Well, no, I did not literally restart my computer. What I did was to run `npm rebuild`. This allowed me to "reset/restart" the node modules (which of course include `node-sass`). It just magically worked. Deleting my node_modules folder and running `npm install` would achieve the same thing.

> When in doubt, run `npm rebuild`

Running `next build` now showed `compiled successfully` and running `next dev` worked: No more blank page... Well, but now we had some __500 Internal Server error__

####  //   FIXME: ... is not a function

If you run the production version, you may encounter ` UnhandledPromiseRejectionWarning: TypeError: ... is not a function`.

After some trials and errors, I noticed it that if I used `ES6 import` instead of `require`, the error went away.

I guessed that for some reason, webpack did not parse `require` correctly. I noticed in my code that I used two different variants: I imported the function by `require` but exported it by `export default`. It might be the cause of the problem.

Therefore, go ahead and change from `require / modules.export` to `import / export`. If you do not specific `export *default*`, you will have to explicitly mention the name of the function. For example:

```javascript
import { nameOfFunction } from 'path/to/theFunction'
```

####  //  FIXME: Cannot overwrite model once compiled

I think it is not actually your error. You may think that it is because you import the model.js file multiple times. Back when I used Express.js, I had to do the same thing but did not encounter this problem. I suspect it was due to Hot Module Replacement (HMS). Because HMS compiles on-the-go, there is a chance that model.js gets compiled more than once, causing the problem.

I tested out my theory by trying to serve a production build using `next build` and `next start`. There was no error because Webpack did not do its compilation then.

Here is a workaround for the problem:

```javascript
export default mongoose.models.User || mongoose.model('User', UserSchema);
```

As you can see, we first see if mongoose.models.User exists and only model it if it does not.

## Hello Next.js API Routes, Goodbye Express.js

### Uninstall redundant dependencies 

Since we are no longer using Express.js, it is always a good idea to remove it.

With Express.js, I also need to uninstall along with two dependencies: [nodemon](https://github.com/remy/nodemon "Nodemon") and [cookie-parser](https://github.com/expressjs/cookie-parser "Cookie Parser"). I 

I used to need `nodemon` to restart my server when I make a change to the code. This is no longer needed as I will use Webpack's Hot Module Replacement from now on.

I used to need `cookie-parser` to access `req.cookies`. This is no longer needed because Next.js 9 has already provided a way to do so.

I went ahead and uninstall them by running:

```bash
npm uninstall express nodemon cookie-parser
```

Make sure to remove any `import / require` of the mentioned dependencies from the code.

### Change the scripts in package.json

In my Express.js version, my scripts were:

```json
"scripts": {
    "dev": "nodemon server/server.js",
    "build": "next build",
    "start": "cross-env NODE_ENV=production node server/server.js",
 }
 ```
For `npm run dev`, I __nodemon__ my custom server `server.js`. For `npm run start`, I __node__ my `server.js`.
 
Moving to API Routes, I no longer need a custom server or hot reloading. All I have to do is run `next dev` and `next start`.

```json
"scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
}
```

## Conclusion

I managed to change my code base to using Next.js API Routes. It opens the possibility of __serverless__, which I will explore soon.

I still run into issues with this new Next.js API Routes now and then. When I do, I will make sure to include it in this article. Good luck deploying your Next.js API Routes.