How to use ES Modules with Node.js
Tags: javascript, node, esm, webdev
Published: 2021-11-03 11:21:38

As most of frontend devs have enjoyed to use ES Modules (or ECMAScript Modules) for a long time, on the backend side of things most of devs still use CommonJS, as it’s still the standard way to import modules.
Since version `8.9.0` you could start to use ES Modules by adding the `—experimental-modules` flag, but you should never use anything experimental in production.

But since Node version 13 you don’t really need to use that flag anymore and as long you use any version from `16` (but it's also supported from version `14.14.0` and `12.20.0`), it’s now fully supported to use, you just need to do a few small things.

In this article we are going to show how to run a small express server using ES Modules.

## Set the right type on your package.json
I created a small sample project in node with just `express` as the only dependency, just as a proof of concept, I created a folder and then initialised a new Node project with `npm init -y`.

Installed Express and nodemon with `npm i express -S` and `npm i nodemon -D`, and added a start script in the `package.json` file, ending up with something like this:

```json
{
  "name": "node-esm",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon index.mjs"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "module",
  "dependencies": {
    "express": "^4.17.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.14"
  }
}
```

Now, if you look closely to the code above, you might have noticed something different, the `type` property and the `index.mjs` file (we’ll discuss later about this file extension).

Regarding the first property, this has two possible values: `commonjs` and  `module`.
The first one is your default value, which just tell to Node to use CommonJS , if you use the `module` option instead is going to tell to Node to use ECMAScript Modules instead.

And this is the only change you need to do in your `package.json` configuration.

## Use the `.mjs` extension

This is the new official extension that also tells to Node that you are going to use a new ES Module, ([more info here](https://nodejs.org/api/esm.html#introduction)).

So in our little project we are going to have our `index.mjs` file, and another file that we are going to export a function to run for an endpoint, again with extension `.mjs`.

Our project file tree will look like this:

```
.
|-- modules
|   `-- test.mjs
|-- index.mjs
|-- package-lock.json
`-- package.json
```

## Add some code to the `.mjs` files

Create a simple `index.mjs` file with just the very basic Express implementation:

```javascript
// index.mjs
import express from 'express';

const app = express();

app.use('/', (req, res) => res.status(200).send('HEALTHY'));

const { SERVER_PORT: port = 5010 } = process.env;

app.listen({ port }, () => {
  console.log(`🚀 Server ready at http://0.0.0.0:${port}`);
});
```

So our `modules/test.mjs` will contain the following code:

```javascript
// modules/test.mjs
export const sayHello = (req, res) => res.json({hello: 'world'});
```

Nothing crazy here, just a function that handles an HTTP request with Express, and just return some sample JSON.

But the nice thing to see here is the `export` keyword!

Now let’s import this file in our `index.mjs`

```javascript
// index.mjs
import express from 'express';
import { sayHello } from './modules/test.mjs';
```

And then use it later in the code:

```javascript
app.get('/hello', sayHello);
```

And our `index.mjs` will look like this now:

```javascript
import express from 'express';
import { sayHello } from './modules/test.mjs';

const app = express();

app.get('/hello', sayHello);
app.use('/', (req, res) => res.status(200).send('HEALTHY'));

const { SERVER_PORT: port = 5010 } = process.env;

app.listen({ port }, () => {
  console.log(`🚀 Server ready at http://0.0.0.0:${port}`);
});

```

Start our application with `npm start` and here we are, our little server running with ES Modules instead of CommonJS :-)

```shell
> node-esm@1.0.0 start
> nodemon index.mjs

[nodemon] 2.0.14
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node index.mjs`
🚀 Server ready at http://0.0.0.0:5010
```

This is great to see finally coming in Node.js, to standardise even more the code between frontend and backend in JavaScript!

## Update 28/04/23

The `.mjs` file extension was a way to enable ES Modules on Node.js from version 13.x, before that you needed an experimental flag, but from Node 18.x onward that's no longer needed and you can just use `.js` files.