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/exportand 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 defaultwhen 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:
| Value | Description |
|---|---|
commonjs | For Node.js projects |
es6 | Modern browser and ESM-compatible tools |
umd | For browser + Node.js hybrid libraries |
amd | For RequireJS |
system | For SystemJS |
esnext | Emit ESNext-style module output |
Example:
{
"compilerOptions": {
"module": "es6",
"target": "es6",
"outDir": "dist",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true
}
}
esModuleInterop: trueenables compatibility with CommonJS-stylerequire()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.jsfiles
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 Case | Module Strategy | Example |
|---|---|---|
| Node.js backend | commonjs | Express, NestJS |
| React front-end (Vite) | esnext | Component-based imports |
| Public NPM libraries | umd or es6 | Publishing for browser & Node |
| Monorepos | paths + aliases | Yarn Workspaces, Lerna, Nx |
| Micro-frontends | moduleFederation | Webpack 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
esModuleInteropfor 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/exportfor modern modular programming - Configure module systems (
commonjs,es6,esnext) viatsconfig.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 exportwhen your module has one main responsibility. - Use
named exportswhen 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 :
