JavaScript Modules: Import and Export Explained
The Story Every Developer Lives Through
You start a new project. One file. index.js. Easy.
A week later it's 300 lines. Still manageable.
A month later? 2,000 lines. Functions named helper, helper2, and helperFinal_v3. A variable called data defined three times. You scroll endlessly looking for that one function you wrote yesterday.
Then your teammate joins. They open the file. They cry.
This is the problem modules were invented to solve.
By the end of this article, you'll understand:
โ Why modules exist in the first place
โ How to export functions, classes, and values
โ How to import them cleanly into other files
โ The difference between default and named exports
โ Why modular code wins in real-world projects
Let's dive in.
1. Why Modules Are Needed
Imagine a small e-commerce app written in a single file:
// index.js โ everything in one file ๐ฑ
function calculateTax(price) { /* ... */ }
function calculateDiscount(price, code) { /* ... */ }
function sendEmail(to, subject, body) { /* ... */ }
function validateUser(user) { /* ... */ }
function connectDatabase() { /* ... */ }
function generateInvoice(order) { /* ... */ }
function loginUser(credentials) { /* ... */ }
// ...500 more functions
This works. Until it doesn't. Here's what breaks:
Problem 1: Naming Collisions
You define validate() for users. Later you write another validate() for products. The second one silently overwrites the first. Welcome to debugging at midnight.
Problem 2: No Reusability
Need that calculateTax function in another project? You have to copy-paste it. Now you have two versions that will inevitably drift apart.
Problem 3: Impossible to Test
Want to test calculateDiscount in isolation? You can't. It's tangled up with database calls, email sending, and global variables.
Problem 4: Team Chaos
Two developers editing the same massive file? Get ready for merge conflicts on every pull request.
Problem 5: Slow to Understand
A new dev joins and asks: "Where's the email logic?" You shrug. "Somewhere around line 1,400, I think."
๐ก The core insight: Code grows. Brains don't. Modules are how we keep complexity manageable.
2. What Is a Module, Really?
A module is just a file that exposes specific pieces of code to the rest of your app, while keeping everything else private.
Think of it like a microwave. You press buttons (the public interface). You don't need to know about the magnetron, capacitor, or circuit board (the private internals).
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ math.js (a module) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ Public: โ
โ โ add() โ
โ โ multiply() โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ Private: โ
โ โข internalLogger() โ
โ โข PI_PRECISION โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ exported to
โโโโโโโโโโโโโโโโ
โ app.js โ
โโโโโโโโโโโโโโโโ
A module decides what to export (make public) and what to keep hidden.
3. Exporting From a Module
Node.js supports two module systems. You'll see both in the wild, so let's cover them both.
CommonJS (the classic Node.js way)
// math.js
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
// Export what you want to expose
module.exports = { add, multiply };
ES Modules (the modern standard)
// math.js
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
โ ๏ธ To use ES Modules in Node.js, either name your file with a
.mjsextension or add"type": "module"to yourpackage.json.
Both achieve the same goal: making add and multiply available to other files.
4. Importing a Module
Now let's actually use math.js from another file.
CommonJS
// app.js
const { add, multiply } = require('./math');
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
ES Modules
// app.js
import { add, multiply } from './math.js';
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
Notice how clean this is? app.js doesn't care how add works internally โ only that it exists and does what it says.
That's the magic. Each file is a tiny, focused unit with a clear job.
5. Named vs Default Exports
This is where most beginners get confused. Let's clear it up.
Named Exports
Use named exports when a module provides multiple things, and you want each one to keep its specific name.
// utils.js
export function formatDate(date) { /* ... */ }
export function formatCurrency(amount) { /* ... */ }
export const TAX_RATE = 0.18;
// app.js
import { formatDate, formatCurrency, TAX_RATE } from './utils.js';
The names in the import statement must match the exported names exactly (unless you rename them with as).
Default Exports
Use a default export when a module's main purpose is one thing โ like a single class, component, or main function.
// User.js
export default class User {
constructor(name) {
this.name = name;
}
}
// app.js
import User from './User.js';
const u = new User("Alice");
Notice โ there are no curly braces when importing a default export, and you can name it anything you want in the importing file:
import Person from './User.js'; // Same thing, different name
import Whatever from './User.js'; // Still works
Mixing Both
A module can have one default export and multiple named exports at the same time:
// auth.js
export default function login(credentials) { /* ... */ }
export function logout() { /* ... */ }
export function isAuthenticated() { /* ... */ }
// app.js
import login, { logout, isAuthenticated } from './auth.js';
Side-by-Side Comparison
| Feature | Named Export | Default Export |
|---|---|---|
| How many per file? | Many | Only one |
| Import syntax | import { x } from '...' |
import x from '...' |
| Can rename freely? | Only with as |
Yes, always |
| Best for | Utility libraries with multiple helpers | Files with one main thing (a class, a component) |
| Typo safety | Stricter โ name must match | Loose โ any name works |
๐ก Rule of thumb: If your file is "a class" or "a component" โ use a default export. If it's "a bag of helpers" โ use named exports. Many teams avoid default exports entirely for stricter consistency. Both choices are valid.
6. Benefits of Modular Code
Once you start writing modular code, you stop wanting to go back. Here's why.
๐งน Cleaner Code Organization
Each file has one clear responsibility. You can find things instantly.
src/
โโโ auth/
โ โโโ login.js
โ โโโ logout.js
โ โโโ validate.js
โโโ utils/
โ โโโ formatDate.js
โ โโโ formatCurrency.js
โโโ db/
โ โโโ connection.js
โโโ app.js
๐ Reusability
Wrote a great formatCurrency function? Drop the file into your next project. Done.
๐งช Easier Testing
Small, focused modules are easy to test in isolation. You can mock dependencies cleanly without dragging in half your codebase.
๐ค Smoother Team Collaboration
Two developers can work on auth/login.js and utils/formatDate.js at the same time โ zero merge conflicts.
๐ Faster Debugging
When something breaks, the bug is contained inside a small file. No more hunting through 2,000 lines.
๐ Performance (in modern setups)
ES Modules support tree-shaking โ unused exports get removed from your final bundle. Smaller bundles = faster apps.
7. A Practical Mini Example
Let's tie everything together. Here's a tiny "user greeter" app, fully modular.
utils/greet.js โ a named-export utility
export function greet(name) {
return `Hello, ${name}!`;
}
export function shout(message) {
return message.toUpperCase() + "!!!";
}
models/User.js โ a default-export class
export default class User {
constructor(name) {
this.name = name;
}
}
app.js โ the entry point pulling it all together
import User from './models/User.js';
import { greet, shout } from './utils/greet.js';
const alice = new User("Alice");
console.log(greet(alice.name)); // Hello, Alice!
console.log(shout(greet(alice.name))); // HELLO, ALICE!!!
Three small, focused files. Each one does one thing well. That's modular thinking.
8. Key Takeaways
๐ก Modules are how professionals write code at scale.
A single giant file becomes a maintenance nightmare. Modules prevent that.
Export what you want to share. Keep everything else private.
Named exports for files with many helpers. Default exports for files with one main thing.
Modular code is easier to read, test, reuse, and collaborate on.
Modern Node.js supports both CommonJS (
require/module.exports) and ES Modules (import/export).
If you're starting a new project today โ go with ES Modules. It's the modern standard, and the rest of the JavaScript ecosystem has fully embraced it.
What's Next?
In upcoming articles, I'll cover:
๐ฅ Module patterns: barrel files, re-exports, and folder structures that scale
๐ฅ Dynamic imports โ loading modules on demand for faster apps
๐ฅ Building and publishing your own npm package
Let's Connect
If this article helped a concept finally click, drop a ๐ and share it with someone still living in index.js hell. Questions and feedback are very welcome โ I read every comment.
Happy coding! ๐
Tags: #javascript #nodejs #webdev #beginners #programming #cleancode