Skip to main content

Command Palette

Search for a command to run...

JavaScript Modules: Import and Export Explained

Updated
โ€ข8 min readโ€ขView as Markdown

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 .mjs extension or add "type": "module" to your package.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

1 views