Skip to main content

Command Palette

Search for a command to run...

JavaScript Modules: Import and Export Explained

Understand JavaScript Modules: Import and Export

Updated
7 min read
JavaScript Modules: Import and Export Explained
A

Hi, I’m Abdul Samad. A web development learner and tech enthusiast. I write about what I learn, share practical coding tips, and publish in-depth blogs on programming and modern web development.

Check out my full collection of blogs on Hashnode: https://abdulsamad30.hashnode.dev/

Connect with me on X for quick updates and insights: @abdul_sama60108

Before JavaScript modules existed, developers faced a messy problem: how do you organize thousands of lines of code without everything turning into chaos?

Why Modules Are Needed

Imagine writing all your code in one giant file. Every function, every variable, every piece of logic crammed together. At first, it's manageable. But as your project grows, you start facing real problems:

The global scope pollution problem

// Everything lives in one file
var userName = "Samad";
var userAge = 17;

function validateUser() {
  // code here
}

function formatUserData() {
  // code here
}

function processPayment() {
  // code here
}

// 500 more functions...

What happens when two different parts of your code accidentally use the same variable name?

Bugs. Hard-to-find bugs. And as your team grows, coordinating who's using which names becomes a nightmare.

Think of it like a house analogy: You wouldn't throw all your belongings into one massive room with no walls, doors, or organization. You'd have separate rooms for different purposes, kitchen for cooking, bedroom for sleeping. Modules are those rooms for your code.

Without modules, you'd also face:

  • No clear way to reuse code across projects

  • Dependency management becomes manual (which script loads first?)

  • Testing individual pieces is nearly impossible

  • Collaboration turns messy when multiple developers touch the same file

Modules solve this by letting you split code into separate files, each with its own scope, and explicitly control what gets shared between them.

Exporting Functions or Values

Once you split your code into separate files, you need a way to make certain parts available to other files. That's what export does, it marks what you want to share.

Named exports let you export multiple things from a single file:

// user.js
export const userName = "Samad";
export const userAge = 17;

export function greetUser() {
  return `Hello, ${userName}!`;
}

export function calculateBirthYear() {
  return new Date().getFullYear() - userAge;
}

Think of exports like a restaurant menu. Not everything in the kitchen is available to customers. The chef decides which dishes make it to the menu. Similarly, you decide which functions or values get exported, the rest stays private to that file.

You can also export everything at once at the bottom of the file:

// math.js
const PI = 3.14159;
const E = 2.71828;

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

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

// Internal helper, not exported
function internalCalculation() {
  return PI * E;
}

export { PI, E, add, multiply };

Notice how internalCalculation isn't exported, it stays private to this file. Other files can't access it.

Importing Modules

After exporting, you need to bring those exports into another file. That's where import comes in.

// app.js
import { userName, greetUser, calculateBirthYear } from './user.js';

console.log(greetUser());
console.log(`Birth year: ${calculateBirthYear()}`);
console.log(`User: ${userName}`);

Output:

Hello, Samad!
Birth year: 2009
User: Samad

Think of imports like ordering from that restaurant menu. You look at what's available (the exports) and specifically request what you need. You don't get access to the entire kitchen, just what the restaurant chose to offer.

You can also import everything from a module using *:

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

console.log(MathUtils.add(5, 3));
console.log(MathUtils.multiply(4, 7));
console.log(MathUtils.PI);

Output:

8
28
3.14159

This creates a namespace (MathUtils) containing all exports from math.js. Useful when you want everything, but still want to keep things organized.

You can also rename imports if there's a naming conflict:

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

console.log(sum(10, 5));
console.log(product(10, 5));

Output:

15
5

Default vs Named Exports

So far we've used named exports. But JavaScript also supports default exports, and understanding when to use each matters.

Default export: One main thing per file

// database.js
export default function connectDatabase() {
  return "Database connected";
}
// app.js
import connectDatabase from './database.js';

console.log(connectDatabase());

Output:

Database connected

Notice: no curly braces when importing a default export. You can also name it whatever you want:

import dbConnect from './database.js'; // still works

Named exports: Multiple things per file

// validators.js
export function validateEmail(email) {
  return email.includes('@');
}

export function validateAge(age) {
  return age >= 18;
}
// app.js
import { validateEmail, validateAge } from './validators.js';

console.log(validateEmail('samad@example.com'));
console.log(validateAge(17));

Output:

true
false

Default export: Think of it like a restaurant's signature dish. When you go to a famous pizza place, there's the pizza everyone comes for that's the main offering, the default. You can order it without specifying much because everyone knows what you mean.

Named exports: Think of it like a food court. Multiple stalls, each offering different items. You have to specifically say "I want dumplings from stall 3 and noodles from stall 7." You're explicitly choosing what you need from the available options.

With default exports, the file says "here's my main thing." With named exports, the file says "here are several things, pick what you need."

You can mix both:

// api.js
export default function fetchData() {
  return "Main API call";
}

export function handleError(error) {
  return `Error: ${error}`;
}

export function formatResponse(data) {
  return `Formatted: ${data}`;
}
// app.js
import fetchData, { handleError, formatResponse } from './api.js';

console.log(fetchData());
console.log(handleError('404 Not Found'));

Output:

Main API call
Error: 404 Not Found

When to use which:

  • Default export: When your file has one primary purpose (a React component, a database connection function, a class)

  • Named exports: When your file provides multiple utilities or related functions

Benefits of Modular Code

Modules transform how you build and maintain JavaScript projects. Here's what you actually gain:

1. Maintainability

// Before modules: everything in app.js (500 lines)
// Finding a bug in payment logic means scrolling through user auth,
// database code, UI updates, etc.

// After modules:
// app.js (50 lines) - main coordination
// auth.js (80 lines) - authentication only
// payment.js (120 lines) - payment logic only
// database.js (60 lines) - database operations only

When a payment bug appears, you know exactly where to look. No digging through unrelated code.

2. Reusability

// validators.js
export function validateEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

export function validatePhone(phone) {
  return /^\d{10}$/.test(phone);
}

Now you can use these validators in:

  • Your signup form

  • Your profile update page

  • Your admin dashboard

  • Your next project entirely

Just import and use. No copy-pasting code across files.

3. Clear dependencies

// order.js
import { validatePayment } from './payment.js';
import { checkInventory } from './inventory.js';
import { sendEmail } from './notifications.js';

export function processOrder(orderData) {
  if (!validatePayment(orderData.payment)) return false;
  if (!checkInventory(orderData.items)) return false;
  sendEmail(orderData.userEmail);
  return true;
}

Just by reading the imports, you immediately know what order.js depends on. No surprises. No hidden global variables causing issues three files away.

4. Easier testing

// math.js
export function calculateDiscount(price, percentage) {
  return price - (price * percentage / 100);
}

// math.test.js
import { calculateDiscount } from './math.js';

console.log(calculateDiscount(100, 10)); // Expected: 90
console.log(calculateDiscount(50, 20));  // Expected: 40

Output:

90
40

You can test calculateDiscount in isolation without loading your entire application. This makes debugging faster and testing more reliable.

5. Team collaboration

When multiple developers work on a project, modules let you divide work cleanly:

  • Developer A works on auth.js

  • Developer B works on dashboard.js

  • Developer C works on api.js

Less merge conflicts. Less "who changed this global variable" debugging sessions.

Think of modules like a well-organized toolbox. Instead of dumping all your tools in one drawer, you have separate compartments: screwdrivers here, wrenches there, hammers in another spot.

When you need a specific tool, you know exactly where it is.

When you buy a new tool, you know where it belongs.

When you lend a tool to someone, you don't have to give them the entire toolbox.

That's what modules do for your code, they provide structure, clarity, and scalability that makes development actually manageable as projects grow.


Final thought: Modules aren't just a feature. They're a fundamental shift in how you think about organizing code. Once you start using them, going back to single-file chaos feels impossible.

FIN✌️