JavaScript Modules: Import and Export Explained
As your JavaScript code grows, keeping everything in one file stops working. Things get hard to find, hard to fix, and hard to reuse. Modules are how JavaScript lets you split your code into separate files and connect them cleanly.
The Problem - One File, Everything Crammed In
Imagine you're building a game. You start with one file:
// game.js
const playerName = "Arjun";
let playerHealth = 100;
let playerLevel = 1;
function greetPlayer() {
console.log(`Welcome, ${playerName}!`);
}
function levelUp() {
playerLevel++;
console.log(`\({playerName} is now level \){playerLevel}`);
}
function takeDamage(amount) {
playerHealth -= amount;
console.log(`Health: ${playerHealth}`);
}
const enemyName = "Zombie";
let enemyHealth = 50;
function attackEnemy() {
enemyHealth -= 20;
console.log(`\({enemyName} health: \){enemyHealth}`);
}
// ...more code keeps getting added
This is fine at first. But two weeks later, this file is 600 lines. Player logic, enemy logic, UI logic, game logic all mixed together.
Now try to find a bug. Now try to reuse just the player logic in another project. Now try to work on this with a teammate without constantly overwriting each other's code.
The single-file approach breaks down fast. You need a way to split code into focused, independent pieces. That's what modules are for.
What a Module Is
A module is just a JavaScript file. Nothing special about the file itself what makes it a module is that it explicitly exports what it wants to share, and other files explicitly import what they need.
Everything else inside the file stays private. By default, nothing leaks out.
Exporting - Sharing What You Want
Named Exports
You can export multiple things from a file by putting export in front of them:
// player.js
export const playerName = "Arjun";
export let playerHealth = 100;
export function greetPlayer() {
console.log(`Welcome, ${playerName}!`);
}
export function levelUp() {
console.log(`${playerName} levelled up!`);
}
Or export everything at the bottom in one go:
// player.js
const playerName = "Arjun";
let playerHealth = 100;
function greetPlayer() {
console.log(`Welcome, ${playerName}!`);
}
function levelUp() {
console.log(`${playerName} levelled up!`);
}
export { playerName, playerHealth, greetPlayer, levelUp };
Both approaches do the same thing. The bottom export style is common because it makes it easy to see everything a file exports in one place.
Default Export
Every module can also have one default export the main thing that file is about:
// player.js
function greetPlayer(name) {
console.log(`Welcome, ${name}!`);
}
export default greetPlayer;
A file can only have one default export. Think of it as the primary thing this module offers.
Importing - Using What You Need
Importing Named Exports
When importing named exports, you use the exact same name wrapped in curly braces:
// game.js
import { playerName, greetPlayer, levelUp } from "./player.js";
greetPlayer(); // Welcome, Arjun!
levelUp(); // Arjun levelled up!
You only import what you need. If a file exports ten things and you only need two, you only bring in two.
Renaming on Import
If a name conflicts with something already in your file, rename it during import:
import { levelUp as playerLevelUp } from "./player.js";
playerLevelUp();
Importing Default Exports
Default exports don't use curly braces, and you can name them anything you want:
// game.js
import greetPlayer from "./player.js";
// or call it whatever you like
import welcome from "./player.js";
welcome("Arjun"); // Welcome, Arjun!
Since there's only one default export per file, JavaScript knows what you're referring to regardless of what you name it on import.
Importing Everything at Once
If you need everything a module exports, use * with an alias:
import * as Player from "./player.js";
Player.greetPlayer();
Player.levelUp();
console.log(Player.playerName);
Everything lives under the Player namespace. Useful when you're using many exports from the same file.
Default vs Named Exports - When to Use Which
This is where people get confused. Here's the practical difference:
Named exports are for files that offer multiple things utility functions, constants, helper methods. The caller picks what they need.
// utils.js
export function formatDate(date) { ... }
export function capitalize(str) { ... }
export function slugify(str) { ... }
Default export is for files that are primarily about one thing a class, a component, a main function.
// PlayerCard.js
export default function PlayerCard({ name, level }) { ... }
| Named Export | Default Export | |
|---|---|---|
| Syntax (export) | export const x = ... |
export default x |
| Syntax (import) | import { x } from ... |
import x from ... |
| How many per file | As many as you want | Only one |
| Name on import | Must match (or rename with as) |
Anything you want |
| Best for | Multiple utilities | One main thing |
You can also mix both in the same file — one default export and several named exports together.
How This Solves the Original Problem
Going back to the messy game.js with modules, you split it up:
game/
├── player.js → player logic only
├── enemy.js → enemy logic only
├── ui.js → display logic only
└── game.js → pulls everything together
// player.js
export const playerName = "Arjun";
export function greetPlayer() { ... }
export function levelUp() { ... }
// enemy.js
export const enemyName = "Zombie";
export function attackEnemy() { ... }
// game.js
import { greetPlayer, levelUp } from "./player.js";
import { attackEnemy } from "./enemy.js";
greetPlayer();
attackEnemy();
levelUp();
game.js is now just the orchestrator. Each file has one job. Each file is short, readable, and independently maintainable.
Benefits of Modular Code
Separation of concerns — player logic lives in player.js, enemy logic lives in enemy.js. Each file has one clear purpose.
Reusability — need the same utility function in another project? Copy that one file, not the entire codebase.
Easier debugging — when something breaks, you know which file to look at. A bug in player health narrows it down to player.js immediately.
Team-friendly — two developers can work on player.js and enemy.js simultaneously without touching the same file.
Private by default — anything you don't export stays inside the file. No accidental variable collisions between files.
One Thing to Keep in Mind
Modules use type="module" in the browser when linking your script:
<script type="module" src="game.js"></script>
Without this, import and export won't work in the browser. In Node.js, you either use .mjs file extensions or add "type": "module" to your package.json.
Bundlers like Vite or Webpack handle this automatically in modern projects — but it's good to know why it's needed.
Final Thought
Modules don't teach you new JavaScript. They teach you how to organise the JavaScript you already know.
A codebase where every function, variable, and logic lives in one file is not a small project with one file it's a large project waiting to become unmaintainable. Splitting code into modules is one of the most impactful habits you can build early, because the cost of not doing it only grows as your project does.
Export what's needed. Import what's used. Keep everything else private. That's it.

