For years, Express has been the go-to framework for building Node.js backends. It’s minimal, flexible, and battle-tested. But if you’ve been writing Express apps for a while, you’ve probably noticed something frustrating:

The good news is that you don’t have to write Express like it’s 2015 anymore.

With ES6 modules (import / export) and Babel, you can write modern JavaScript on the server, keep your codebase consistent with frontend tooling, and still run smoothly in Node.

In this post, we’ll walk step-by-step through setting up an Express server using ES6 and Babel, explain why each piece exists, and finish with a scalable project structure you can actually grow into.


Why Use ES6 with Express?

Before diving into setup, it’s worth understanding why this approach is still relevant.

Cleaner syntax

Compare this:

const express = require("express");
const router = require("./routes");

With this:

import express from "express";
import router from "./routes";

ES6 imports are:

Consistency across your stack

If you’re already using ES6+ in React, Vue, or Angular, switching back to CommonJS on the server is mental overhead you don’t need. Using ES6 everywhere makes onboarding easier and code reviews faster.

Future-proofing

Native ES modules in Node are improving, but Babel still provides:


What We’re Building

By the end of this tutorial, you’ll have:


Step 1: Initialize the Project

Start by creating a new project directory:

mkdir express-es6-babel
cd express-es6-babel
npm init -y

Install Express:

npm install express

At this point, you have a standard Node project with Express installed—but we’ll modernize it next.


Step 2: Install Babel

Babel is responsible for converting modern JavaScript syntax into something Node can run reliably.

Install the required Babel packages:

npm install --save-dev @babel/core @babel/cli @babel/preset-env

What these packages do


Step 3: Configure Babel

Create a .babelrc file in the project root:

{
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "node": "current"
      }
    }]
  ]
}

This tells Babel:

“Transpile my code for the Node version I’m currently running.”

This is important because server code doesn’t need to support legacy browsers—just your Node runtime.


Step 4: Create the Source Structure

Instead of dumping everything in the root, we’ll follow a simple convention:

mkdir src
touch src/index.js

Your compiled output will later live in dist/.


Step 5: Write Your First ES6 Express Server

Open src/index.js and add:

import express from "express";

const app = express();

app.get("/", (req, res) => {
  res.json({
    success: true,
    message: "Express running with ES6 and Babel"
  });
});

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`Server listening on http://localhost:${PORT}`);
});

This is already cleaner than most legacy Express setups:


Step 6: Add Build and Run Scripts

Open package.json and update your scripts section:

{
  "scripts": {
    "build": "babel src -d dist",
    "start": "node dist/index.js",
    "dev": "nodemon --watch src --exec \"npm run build && node dist/index.js\""
  }
}

What these scripts do

Install Nodemon if you haven’t already:

npm install --save-dev nodemon

Step 7: Run the Server

Start the development server:

npm run dev

Open your browser at:

http://localhost:3000

You should see:

{
  "success": true,
  "message": "Express running with ES6 and Babel"
}

At this point, you have a fully working Express backend using ES6 modules.


Step 8: Ignore Generated Files

Create a .gitignore file:

node_modules
dist
.env

The dist/ folder should always be generated, never committed.


Step 9: Scaling Beyond a Single File

Real Express apps don’t live in index.js forever. Let’s introduce a more realistic structure.

Recommended folder layout

src/
  index.js
  routes/
    index.js
  controllers/
    healthController.js

Controllers

src/controllers/healthController.js

export function healthCheck(req, res) {
  res.json({
    status: "ok",
    uptime: process.uptime()
  });
}

Controllers handle business logic, not routing.


Routes

src/routes/index.js

import { Router } from "express";
import { healthCheck } from "../controllers/healthController.js";

const router = Router();

router.get("/health", healthCheck);

export default router;

Routes define URLs, not logic.


Wire Everything Together

Update src/index.js:

import express from "express";
import router from "./routes/index.js";

const app = express();

app.use(express.json());
app.use(router);

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`Server listening on port ${PORT}`);
});

Now your API is structured, readable, and ready to grow.


Common Errors and How to Avoid Them

“Cannot use import statement outside a module”

This happens when Node tries to run uncompiled ES6 code.

Fix:
Always run node dist/index.js, not src/index.js.


Import paths breaking after build

Use relative imports and keep folder structure consistent between src and dist.


Why not just use Node’s native ES modules?

You can—but Babel still offers:

For many teams, Babel remains the most friction-free solution.


When This Setup Makes Sense

This Express + ES6 + Babel setup is ideal if:

If you’re writing a throwaway script, this is probably overkill. For anything long-lived, it pays off quickly.


Final Thoughts

Express doesn’t have to feel outdated.

With Babel and ES6, you get:

Once you’ve used this setup, going back to raw CommonJS feels like a step backward.

If you want to take this further, the next natural steps are:

If you want any of those as follow-up posts or code templates, just comment below.

Leave a Reply

Your email address will not be published. Required fields are marked *