DEV Community

Cover image for JavaScript Modules: Import and Export Explained
Pratham
Pratham

Posted on

JavaScript Modules: Import and Export Explained

How to break your code into organized, reusable pieces — and why it changes everything.


Here's what my first JavaScript project looked like: one file, 500 lines, everything jammed together. Helper functions at the top, API calls in the middle, DOM manipulation at the bottom, and a dozen global variables floating around connecting it all.

It worked. But the moment I needed to change something, I was scrolling up and down for ten minutes trying to find where that one function was. And when I accidentally named two variables the same thing? Silent bugs that took an hour to track down.

This is the problem modules solve. Instead of one massive file, you split your code into smaller, focused files — each responsible for one thing. Then you import only what you need, where you need it. No scrolling, no naming conflicts, no chaos.

In the ChaiCode Web Dev Cohort 2026, modules were the moment my code started looking like something a professional would write. Let me show you how they work.


Why Are Modules Needed?

Let's start with the pain points that modules fix.

Problem 1: Everything in One File

app.js (800 lines)
├── Line 1-50:    Utility functions
├── Line 51-200:  User authentication logic
├── Line 201-400: Product listing logic
├── Line 401-600: Cart and checkout logic
└── Line 601-800: DOM manipulation and event handlers
Enter fullscreen mode Exit fullscreen mode

Finding anything requires scrolling through hundreds of lines. Making a change to one feature risks breaking another. And if two developers work on this file simultaneously? Merge conflict nightmare.

Problem 2: Global Variable Pollution

Without modules, every variable and function lives in the global scope. This means:

// In one part of the file
let count = 0; // user count

// ... 200 lines later ...
let count = 10; // product count — OOPS, overwritten the first one!
Enter fullscreen mode Exit fullscreen mode

The more code you write, the higher the chance of name collisions. And in the global scope, everything is accessible to everything — there's no isolation.

Problem 3: No Reusability

What if you write a great formatPrice() function in one project and want to use it in another? You'd have to copy-paste it. If you fix a bug in the original, you have to fix it in every copy. That doesn't scale.

The Solution: Modules

Before (one massive file):
┌──────────────────────────────────────┐
│              app.js                  │
│  utilities + auth + products + cart  │
│  + DOM + events + everything else    │
│           (800 lines)                │
└──────────────────────────────────────┘

After (organized modules):
┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│  utils.js    │  │   auth.js    │  │  cart.js     │
│  (50 lines)  │  │  (100 lines) │  │  (80 lines)  │
└──────┬───────┘  └──────┬───────┘  └──────┬───────┘
       │                 │                 │
       └────────┬────────┴────────┬────────┘
                ↓                 ↓
         ┌──────────────────────────────┐
         │           app.js             │
         │  imports what it needs       │
         │  (50 lines — clean & focused)│
         └──────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Each file has its own scope. Variables don't leak. Functions are reusable. Everything is organized.


Exporting Functions and Values

To make something from one file available to other files, you export it. JavaScript gives you two ways to export: named exports and default exports.

Named Exports

You can export multiple things from a single file by putting export in front of each one.

// math.js

export const add = (a, b) => a + b;

export const subtract = (a, b) => a - b;

export const multiply = (a, b) => a * b;

export const PI = 3.14159;
Enter fullscreen mode Exit fullscreen mode

Or you can export them all at the bottom:

// math.js

const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
const multiply = (a, b) => a * b;
const PI = 3.14159;

export { add, subtract, multiply, PI };
Enter fullscreen mode Exit fullscreen mode

Both approaches are identical. I prefer the second style for larger files — it gives you one place to see everything that's exported.

Default Exports

Each file can have one default export. It's the "main thing" that file provides.

// greet.js

const greet = (name) => `Hello, ${name}!`;

export default greet;
Enter fullscreen mode Exit fullscreen mode

You can also export it inline:

// greet.js

export default (name) => `Hello, ${name}!`;
Enter fullscreen mode Exit fullscreen mode

Named vs Default — Side by Side

// utils.js — mixing both

// Named exports (as many as you want)
export const formatPrice = (price) => `₹${price.toFixed(2)}`;
export const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);

// Default export (only one per file)
const utils = {
  version: "1.0.0",
  author: "Pratham",
};
export default utils;
Enter fullscreen mode Exit fullscreen mode

Importing Modules

Exporting makes things available. Importing brings them into another file.

Importing Named Exports

Use curly braces { } and the exact name used in the export:

// app.js

import { add, subtract, PI } from "./math.js";

console.log(add(10, 5)); // 15
console.log(subtract(10, 5)); // 5
console.log(PI); // 3.14159
Enter fullscreen mode Exit fullscreen mode

You only import what you need. If math.js has 20 exports but you only need add, just import add. The rest stay out of your scope.

Importing Default Exports

No curly braces needed. You can name it anything you want:

// app.js

import greet from "./greet.js";

console.log(greet("Pratham")); // "Hello, Pratham!"
Enter fullscreen mode Exit fullscreen mode

Since it's the default export, you're free to call it whatever makes sense:

import sayHello from "./greet.js"; // Same thing, different name
console.log(sayHello("Pratham")); // "Hello, Pratham!"
Enter fullscreen mode Exit fullscreen mode

Importing Both Named and Default

// app.js

import utils, { formatPrice, capitalize } from "./utils.js";

console.log(utils.version); // "1.0.0"
console.log(formatPrice(499)); // "₹499.00"
console.log(capitalize("pratham")); // "Pratham"
Enter fullscreen mode Exit fullscreen mode

The default comes first (no braces), then named exports in { }.

Renaming Imports with as

If a name conflicts with something in your file, rename it:

import { add as sum, subtract as minus } from "./math.js";

console.log(sum(10, 5)); // 15
console.log(minus(10, 5)); // 5
Enter fullscreen mode Exit fullscreen mode

Importing Everything with *

If you need everything from a module:

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

console.log(MathUtils.add(10, 5)); // 15
console.log(MathUtils.PI); // 3.14159
Enter fullscreen mode Exit fullscreen mode

All exports become properties on the MathUtils object.


Module Import/Export Flow

Here's the complete picture of how data flows between files:

┌─────────────────────────┐         ┌─────────────────────────┐
       math.js                            helpers.js         
                                                             
  export const add = ...             export default          
  export const sub = ...               function format() {}  
  export const PI = 3.14                                     
└────────────┬─────────────┘         └────────────┬─────────────┘
                                                 
    named exports                        default export
                                                 
                                                 
┌──────────────────────────────────────────────────────────────┐
                          app.js                              
                                                              
  import { add, PI } from "./math.js";                        
  import format from "./helpers.js";                          
                                                              
  console.log(add(2, 3));    // 5                             │
  console.log(PI);           // 3.14                          │
  console.log(format(...));  // formatted output              │
└──────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Each file exports what it offers. The consuming file imports only what it needs. Clean, explicit, and traceable.


Default vs Named Exports — When to Use Which

Feature Named Export Default Export
How many per file ✅ Unlimited ❌ Only one
Import syntax { name } (exact match) anyName (your choice)
Renaming { name as alias } Name it anything on import
Best for Multiple utilities from one file The main "thing" a file provides
Refactoring safety ✅ Name must match — typos caught ⚠️ Any name works — typos silent
Common in Utility/helper files Components, classes, configs

My Guideline

  • File exports one main thing (a component, a class, a primary function) → default export
  • File exports multiple utilities (helper functions, constants, validators) → named exports
  • Mixing both → default for the main thing, named for the extras
// ✅ Default — one main component
// Button.js
export default function Button({ label }) {
  return `<button>${label}</button>`;
}

// ✅ Named — multiple utility functions
// validators.js
export const isEmail = (str) => str.includes("@");
export const isNotEmpty = (str) => str.trim().length > 0;
export const isMinLength = (str, min) => str.length >= min;
Enter fullscreen mode Exit fullscreen mode

File Dependency Diagram — A Real Project

Here's what modules look like in a realistic project structure:

project/
├── index.js              ← entry point
├── config.js             ← app configuration
├── utils/
│   ├── format.js         ← formatting utilities
│   └── validate.js       ← validation helpers
├── services/
│   ├── auth.js           ← authentication logic
│   └── api.js            ← API communication
└── components/
    ├── Header.js          ← header component
    └── Footer.js          ← footer component
Enter fullscreen mode Exit fullscreen mode
Dependency flow:

  config.js ──────────────────────────────┐
       ↓                                  │
  api.js ← imports config                 │
       ↓                                  │
  auth.js ← imports api                   │
       ↓                                  │
  index.js ← imports auth, Header, Footer,│config
       ↑                                  │
  Header.js ← imports format ─────────────┘
  Footer.js ← imports format

Each file imports ONLY what it needs.
No file needs to know about files it doesn't use.
Enter fullscreen mode Exit fullscreen mode

Benefits of Modular Code

Let me lay out why modules aren't just "nice to have" — they're essential:

Benefit Explanation
Organization Each file has a single responsibility. Finding code is instant.
No naming conflicts Each module has its own scope. Two files can have a count variable.
Reusability Write once, import anywhere — across files and projects.
Maintainability Change one module without touching the rest of the app.
Team collaboration Developers work on separate files without merge conflicts.
Testability Test each module independently with its own inputs and outputs.
Lazy loading Load modules only when needed — better performance.

Before Modules vs After Modules

// ❌ Before — everything global, no structure
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
function formatPrice(price) { return "" + price; }
function validateEmail(email) { return email.includes("@"); }
// ... 50 more functions, all global, all in one file

// ✅ After — separated, scoped, importable
// math.js       → add, subtract
// format.js     → formatPrice
// validate.js   → validateEmail
// app.js        → imports only what it needs
Enter fullscreen mode Exit fullscreen mode

Setting Up Modules in Practice

In the Browser

Add type="module" to your script tag:

<script type="module" src="app.js"></script>
Enter fullscreen mode Exit fullscreen mode

Without type="module", import/export won't work and you'll get a syntax error.

In Node.js

Either add "type": "module" to your package.json:

{
  "type": "module"
}
Enter fullscreen mode Exit fullscreen mode

Or use the .mjs file extension instead of .js.


Let's Practice: Hands-On Assignment

Part 1: Create a Math Module

// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => (b !== 0 ? a / b : "Cannot divide by zero");
Enter fullscreen mode Exit fullscreen mode

Part 2: Create a Greeting Module with Default Export

// greet.js
const greet = (name, time = "day") => {
  const greetings = {
    morning: `Good morning, ${name}! ☀️`,
    afternoon: `Good afternoon, ${name}! 🌤️`,
    evening: `Good evening, ${name}! 🌇`,
    day: `Hello, ${name}! 👋`,
  };
  return greetings[time] || greetings.day;
};

export default greet;
Enter fullscreen mode Exit fullscreen mode

Part 3: Import and Use Both in app.js

// app.js
import greet from "./greet.js";
import { add, multiply } from "./math.js";

console.log(greet("Pratham", "morning"));
// "Good morning, Pratham! ☀️"

console.log(`10 + 5 = ${add(10, 5)}`);
// "10 + 5 = 15"

console.log(`4 × 7 = ${multiply(4, 7)}`);
// "4 × 7 = 28"
Enter fullscreen mode Exit fullscreen mode

Part 4: Mix Named and Default, Use Renaming

// app.js
import greet, { add as sum, subtract as minus } from "./math.js";
// Note: this assumes math.js has a default export too

// Or import everything
import * as Math from "./math.js";
console.log(Math.add(3, 4)); // 7
console.log(Math.divide(10, 3)); // 3.333...
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. Modules split your code into separate files, each with its own scope. No global pollution, no naming conflicts, no 800-line monster files.
  2. export makes functions, variables, or values available to other files. Use named exports for multiple items and default export for the main item.
  3. import brings exported values into your file. Named imports use { } with exact names. Default imports can be named anything.
  4. Named exports are safer for refactoring (names must match). Default exports are flexible (name them anything on import).
  5. Modules make code organized, reusable, testable, and maintainable — which is why every modern framework (React, Vue, Node.js) is built entirely around them.

Wrapping Up

Modules were the point where my code stopped looking like a student project and started looking like something professional. The concepts are simple — export what you want to share, import what you need — but the impact on code quality is massive. Cleaner files, fewer bugs, easier collaboration, and code you can actually navigate.

I'm building all of this through the ChaiCode Web Dev Cohort 2026 under Hitesh Chaudhary and Piyush Garg, and modules became essential the moment we started working with React, where every component is a module. Understanding import/export now means you won't be confused later.

Connect with me on LinkedIn or visit PrathamDEV.in. More articles on the way as the journey continues.

Happy coding! 🚀


Written by Pratham Bhardwaj | Web Dev Cohort 2026, ChaiCode

Top comments (0)