TypeScript – Modules Explained with Examples and Configuration

Introduction – Why Learn Modules in TypeScript?

As applications grow in complexity, managing and organizing code efficiently becomes critical. This is where TypeScript Modules shine. Modules help you organize, isolate, and reuse code in a scalable and maintainable way, especially for large projects with multiple contributors.

In this comprehensive guide, you’ll learn:

  • What TypeScript modules are
  • Difference between internal vs. external modules
  • Module syntax: import, export, default
  • How to configure the module system in tsconfig.json
  • Real-world use cases and best practices

What Are Modules in TypeScript?

A module in TypeScript is any file that contains an import or export statement. By default, each file is its own module with its own scope, meaning variables, functions, and classes declared in one file won’t pollute the global scope.

Why Use Modules?

  • Avoid global namespace pollution
  • Encapsulate logic and data
  • Split code into reusable parts
  • Improve maintainability and readability

Internal vs. External Modules

Before TypeScript 1.5, TypeScript distinguished between internal modules (now called namespaces) and external modules (now simply called modules).

  • Modules use import/export and align with ES6 module standards.
  • Namespaces are legacy and discouraged for modern codebases.

Use modules, not namespaces, for all new TypeScript projects.


Exporting from Modules

You can export values from a module using either named exports or a default export.

Named Exports

// math.ts
export const PI = 3.14;
export function add(a: number, b: number): number {
  return a + b;
}
export class Calculator {
  subtract(a: number, b: number) {
    return a - b;
  }
}

In another file:

// app.ts
import { PI, add, Calculator } from "./math";

console.log(PI); // 3.14
console.log(add(5, 3)); // 8
  • Use named exports when you have multiple exports per file.
  • They allow you to selectively import only what you need.

Default Export

// logger.ts
export default function log(message: string): void {
  console.log(`[LOG]: ${message}`);
}

In another file:

// app.ts
import log from "./logger";

log("Hello from default export!");
  • Use export default when the module has a single main export.
  • You can name the import anything you like.

Mixing Named and Default Exports

Though possible, avoid mixing both export styles in the same module for consistency.

// mixed.ts
export const version = "1.0";
export default function init() {
  console.log("Initializing...");
}

In another file:

import init, { version } from "./mixed";

Re-exporting From Another Module

You can re-export values from another module:

// geometry.ts
export { PI, add } from "./math";

This allows you to build aggregator modules or libraries with public APIs.


Configuring Modules with tsconfig.json

The module system TypeScript emits depends on your tsconfig.json setting.

Common module options:

ValueDescription
commonjsFor Node.js projects
es6Modern browser and ESM-compatible tools
umdFor browser + Node.js hybrid libraries
amdFor RequireJS
systemFor SystemJS
esnextEmit ESNext-style module output

Example:

{
  "compilerOptions": {
    "module": "es6",
    "target": "es6",
    "outDir": "dist",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "strict": true
  }
}
  • esModuleInterop: true enables compatibility with CommonJS-style require() imports.

Module Resolution Strategies

TypeScript can resolve modules using one of two strategies:

node (default)

  • Mimics Node.js resolution
  • Looks for .ts, .tsx, .d.ts, then .js files

classic

  • TypeScript’s legacy behavior, now rarely used

Use node resolution strategy unless you have specific legacy needs.


Module Scope and Encapsulation

Each module has its own scope:

  • Variables, functions, and types declared in one module are not visible globally.
  • You must explicitly import what you need.
// config.ts
export const API_URL = "https://api.example.com";
// service.ts
import { API_URL } from "./config";

No risk of API_URL leaking into the global namespace.


Module Aliases for Cleaner Imports

Deep relative paths can get ugly:

import { doSomething } from "../../../../utils/helpers";

You can define aliases in tsconfig.json using paths and baseUrl:

{
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "@utils/*": ["utils/*"]
    }
  }
}

Then use:

import { doSomething } from "@utils/helpers";

Boosts code readability and makes refactoring easier.


Real-World Applications of Modules

Use CaseModule StrategyExample
Node.js backendcommonjsExpress, NestJS
React front-end (Vite)esnextComponent-based imports
Public NPM librariesumd or es6Publishing for browser & Node
Monorepospaths + aliasesYarn Workspaces, Lerna, Nx
Micro-frontendsmoduleFederationWebpack 5, Module federation

Best Practices for Using Modules

  • Use one export strategy (either named or default) per file
  • Favor named exports for larger modules
  • Use index.ts to re-export multiple modules from a folder
  • Avoid require() unless working with legacy CommonJS code
  • Configure esModuleInterop for compatibility

Summary – Recap & Key Takeaways

Modules are fundamental to organizing and scaling TypeScript projects. By understanding the module system, you can write cleaner, more reusable, and more maintainable code.

Key Points:

  • Modules isolate code and promote reusability
  • Use import/export for modern modular programming
  • Configure module systems (commonjs, es6, esnext) via tsconfig.json
  • Use aliases to simplify import paths
  • Combine with interfaces, types, and classes for full power

FAQs

Can I use ES6 imports with Node.js?

Yes, with "module": "es6" and proper runtime support (e.g., Node.js 14+ with "type": "module" in package.json).


Should I use default export or named export?

  • Use default export when your module has one main responsibility.
  • Use named exports when you want to export multiple utilities or values.

How do I share types between modules?

Export them:

// types.ts
export type User = { id: number; name: string };

Import in another file:

import { User } from "./types";

What is moduleResolution?

It tells TypeScript how to find your files. Use node to mimic Node.js behavior, which is the most common setup.


Share Now :
Share

TypeScript — Modules

Or Copy Link

CONTENTS
Scroll to Top