Skip to main content

Command Palette

Search for a command to run...

JavaScript Modules: Import and Export Explained

Updated
•9 min read•View as Markdown

The Problem: JavaScript Without Modules

Picture this: you're building a web app. You start with one file — app.js. It's clean, manageable, maybe 50 lines. Three weeks later, it's 2,000 lines of functions, variables, event listeners, and utility helpers all tangled together. You want to reuse a date formatting function in another project, but it's buried somewhere in the middle, tightly coupled with everything else.

This is what JavaScript development looked like for a long time. And it created real problems:

  • Global namespace pollution: Every variable and function lives in the global scope. Name a function format in one script, include another library that also has a format function, and one silently overwrites the other.

  • No clear ownership: Which file is responsible for which logic? Nobody knows.

  • Impossible to reuse: Logic is tangled with other logic. You can't take just the part you need.

  • Maintenance nightmare: Changing one thing breaks something unrelated, three files away.

Modules solve all of this. They let you split code into focused files, each with its own private scope, and explicitly declare what gets shared and what stays internal.


How Modules Work: The Core Idea

Before diving into syntax, understand the mental model:

A module is a file. Every file is its own private scope. Nothing inside it is visible to the outside world — unless you explicitly export it. And nothing from the outside world enters — unless you explicitly import it.

That's it. Export what you want to share. Import what you need. Everything else stays private.

┌─────────────────────────────────────────┐
│              math.js                    │
│                                         │
│  const PI = 3.14159  ← private         │
│  const secret = 42   ← private         │
│                                         │
│  export function add() { ... }  ← public│
│  export function multiply() {...}← public│
└──────────────────┬──────────────────────┘
                   │  exports travel here
                   ▼
┌─────────────────────────────────────────┐
│              app.js                     │
│                                         │
│  import { add, multiply } from './math' │
│                                         │
│  // PI and secret are invisible here    │
└─────────────────────────────────────────┘

Part 1: Exporting — Sharing What You Want

There are two kinds of exports in JavaScript: named exports and default exports. Understanding the difference is the key to using modules confidently.

Named Exports

Named exports let you export multiple things from one file, each with its own name.

// math.js

export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}

// This stays private — not exported
function internalHelper() {
  return 'only I can use this';
}

You can also declare everything first and export them together at the bottom — many developers prefer this because it gives you a clear "public API" view at a glance:

// math.js

const PI = 3.14159;

function add(a, b) {
  return a + b;
}

function multiply(a, b) {
  return a * b;
}

// Clean list of everything this file exposes
export { PI, add, multiply };

Both styles are equivalent. Pick one and stay consistent within a project.

Default Exports

A default export is for when a file has one main thing to offer. A class, a component, a primary function. Each file can have only one default export.

// formatDate.js

export default function formatDate(date) {
  return new Intl.DateTimeFormat('en-IN', {
    day: 'numeric',
    month: 'long',
    year: 'numeric',
  }).format(date);
}

Or with a class:

// UserService.js

export default class UserService {
  getUser(id) { /* ... */ }
  createUser(data) { /* ... */ }
  deleteUser(id) { /* ... */ }
}

Part 2: Importing — Taking What You Need

Importing Named Exports

Use curly braces {} and the exact name:

// app.js

import { add, multiply } from './math.js';

console.log(add(2, 3));       // 5
console.log(multiply(4, 5));  // 20

The names in the braces must match exactly what was exported. You can import one, some, or all of them — your choice.

Renaming on import with as — useful when names conflict or you want something shorter:

import { add as sum, multiply as times } from './math.js';

console.log(sum(2, 3));    // 5
console.log(times(4, 5));  // 20

Namespace import — pull everything from a module into a single object:

import * as MathUtils from './math.js';

console.log(MathUtils.add(2, 3));
console.log(MathUtils.PI);

Importing Default Exports

No curly braces. You choose the name — any name works:

// app.js

import formatDate from './formatDate.js';

console.log(formatDate(new Date())); // 7 May 2026

Because there's only one default export per file, JavaScript knows exactly what you're asking for. The name you give it locally is entirely up to you.

Mixing Both in One Import

You can import a default export and named exports from the same file in one line:

// utils.js
export default function log(msg) { console.log(msg); }
export const VERSION = '1.0.0';
export function warn(msg) { console.warn(msg); }
import log, { VERSION, warn } from './utils.js';

log('App started');
warn('Low memory');
console.log(VERSION); // 1.0.0

Default comes first, named follow in braces.


Part 3: Default vs Named Exports — When to Use Which

This is where most beginners get confused. Here's a clear mental model:

Default Export Named Export
How many per file Only one As many as you want
Import syntax import anything from './file' import { exactName } from './file'
Name flexibility Importer chooses the name Must match the exported name
Best for The main purpose of a file Utilities, constants, helpers
Discoverability Lower (name can be anything) Higher (name is enforced)

A Practical Rule of Thumb

Use default export when the file is one thing — a React component, a class, a primary function. The filename tells you what it is.

// Button.js → default export makes sense, the file IS the button
export default function Button({ label, onClick }) { ... }

Use named exports when the file contains multiple related things — a utilities file, a constants file, a set of helper functions.

// validators.js → named exports make sense, file has many things
export function isEmail(str) { ... }
export function isPhoneNumber(str) { ... }
export function isNotEmpty(str) { ... }

Part 4: File Dependency Flow in a Real Project

Here's how modules connect in a typical project structure:

src/
├── utils/
│   ├── math.js          → exports: add, subtract, multiply
│   ├── formatDate.js    → exports: formatDate (default)
│   └── validators.js    → exports: isEmail, isNotEmpty
│
├── services/
│   └── userService.js   → imports from validators.js
│                           exports: UserService (default)
│
├── components/
│   └── UserCard.js      → imports from formatDate.js, userService.js
│                           exports: UserCard (default)
│
└── app.js               → imports from UserCard.js
                           (the entry point — assembles everything)

Data flows in one direction. Each file knows only what it explicitly imports. math.js has no idea app.js exists. This isolation is exactly the point.

math.js ──────────────────────────────────────────┐
                                                   │
formatDate.js ──────────────────┐                 │
                                ▼                 ▼
validators.js ──► userService.js ──► UserCard.js ──► app.js

Dependencies flow inward toward the entry point. Low-level utilities know nothing about high-level components. This is what makes codebases maintainable as they grow.


Part 5: Benefits of Modular Code

1. Scoped Variables — No More Global Collisions

// Without modules (old way, both in global scope)
var format = function() { /* your version */ };
var format = function() { /* library's version — silently overwrites yours */ };

// With modules (each file has its own scope)
// format in math.js and format in date.js never meet

2. Explicit Dependencies

When you look at the top of a file, you see exactly what it depends on. No guessing, no tracing through HTML script tags.

// This file's entire dependency list, right here at the top
import { isEmail } from './validators.js';
import UserService from './services/userService.js';
import { API_URL } from './config.js';

3. Reusability Across Projects

A well-written module can be dropped into any project without dragging unrelated code with it. Your formatDate.js is self-contained — copy it anywhere and it works.

4. Easier Testing

Private implementation details stay private. You test only what's exported — the public interface. Internal helper functions don't leak out and don't need to be tested separately unless you want them to.

// In your test file
import { add, multiply } from './math.js';

// internalHelper is not exported, not your problem to test
test('add works', () => expect(add(2, 3)).toBe(5));

5. Maintainability at Scale

When something breaks, you know exactly which module to look in. When you need to change how dates are formatted across the entire app, you change one file. When a new developer joins the team, the file structure tells the story of how the application is organized.


A Note on Module Syntax: ES Modules vs CommonJS

You may encounter two different syntaxes in the wild:

// ES Modules (modern — what this article covers)
import { add } from './math.js';
export function add(a, b) { return a + b; }

// CommonJS (older Node.js style)
const { add } = require('./math.js');
module.exports = { add };

ES Modules (import/export) is the official JavaScript standard, supported natively in modern browsers and Node.js (v12+). CommonJS (require/module.exports) is the older Node.js convention and still widely used in backend Node code.

For new projects, ES Modules is the way to go. If you're working in Node.js specifically, you'll need either a .mjs extension or "type": "module" in your package.json to use ES Module syntax.

2 views