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 syntax often feels stuck in the past
require()andmodule.exportsclutter otherwise clean code- Mixing frontend ES6 with backend CommonJS feels inconsistent
- Large Express apps become hard to structure and scale
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:
- More readable
- Easier to refactor
- Standardized across frontend and backend
- Better supported by modern tooling
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:
- broader compatibility
- predictable builds
- easier debugging in production
What We’re Building
By the end of this tutorial, you’ll have:
- An Express server written entirely in ES6
- Babel compiling your server code
- A clean
src/→dist/build pipeline - A dev workflow with automatic restarts
- A structure that scales beyond a single file
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
- @babel/core – the Babel engine
- @babel/cli – allows running Babel from the command line
- @babel/preset-env – automatically chooses the right transformations based on your target environment
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:
- No
require - No
module.exports - Clear separation between configuration and execution
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
- build – compiles all files from
src/intodist/ - start – runs the compiled server (production-style)
- dev – rebuilds and restarts on every file change
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:
- predictable builds
- easier deployment
- better compatibility with older tooling
- smoother debugging
For many teams, Babel remains the most friction-free solution.
When This Setup Makes Sense
This Express + ES6 + Babel setup is ideal if:
- You’re building APIs or microservices
- You want clean imports across your stack
- You plan to add TypeScript later
- You care about maintainability and structure
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:
- modern syntax
- cleaner architecture
- scalable structure
- a development experience that matches modern frontend tooling
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:
- adding TypeScript
- introducing ESLint + Prettier
- Dockerizing the app
- deploying with a CI/CD pipeline
If you want any of those as follow-up posts or code templates, just comment below.