Module 10: Modules and Namespaces
Learn how to organize and structure TypeScript code using modules and namespaces for maintainable, scalable applications.
1. ES6 Modules
TypeScript fully supports ES6 module syntax.
Exporting
// user.ts
export interface User {
id: number;
name: string;
email: string;
}
export function createUser(name: string, email: string): User {
return {
id: Math.random(),
name,
email
};
}
export class UserService {
getUser(id: number): User | undefined {
// Implementation
return undefined;
}
}
export const DEFAULT_ROLE = "user";
Importing
// app.ts
import { User, createUser, UserService, DEFAULT_ROLE } from "./user";
let user = createUser("Alice", "alice@example.com");
let service = new UserService();
2. Default Exports
// logger.ts
export default class Logger {
log(message: string): void {
console.log(`[LOG] ${message}`);
}
}
// app.ts
import Logger from "./logger";
let logger = new Logger();
logger.log("Application started");
Use default exports when a module has a single primary export. Use named exports for modules with multiple exports.
3. Re-exporting
// models/user.ts
export interface User {
id: number;
name: string;
}
// models/product.ts
export interface Product {
id: number;
name: string;
price: number;
}
// models/index.ts
export { User } from "./user";
export { Product } from "./product";
export * from "./order"; // Re-export all
// app.ts
import { User, Product } from "./models";
4. Import Aliases
import { UserService as US } from "./services/user";
import { UserService as AdminUserService } from "./services/admin/user";
let userService = new US();
let adminService = new AdminUserService();
5. Type-Only Imports/Exports
Import only types (no runtime code).
// types.ts
export interface User {
name: string;
}
export function createUser(name: string): User {
return { name };
}
// app.ts
import type { User } from "./types";
import { createUser } from "./types";
let user: User = createUser("Alice");
6. Namespace
Namespaces provide logical grouping (older pattern, modules preferred).
namespace Validation {
export interface StringValidator {
isValid(s: string): boolean;
}
export class EmailValidator implements StringValidator {
isValid(email: string): boolean {
return email.includes("@");
}
}
export class URLValidator implements StringValidator {
isValid(url: string): boolean {
return url.startsWith("http");
}
}
}
// Usage
let emailValidator = new Validation.EmailValidator();
console.log(emailValidator.isValid("test@example.com")); // true
7. Nested Namespaces
namespace Company {
export namespace HR {
export class Employee {
constructor(public name: string, public id: number) {}
}
export function hireEmployee(name: string): Employee {
return new Employee(name, Math.floor(Math.random() * 10000));
}
}
export namespace IT {
export class Developer extends HR.Employee {
constructor(name: string, id: number, public programmingLanguage: string) {
super(name, id);
}
}
}
}
let employee = Company.HR.hireEmployee("Alice");
let developer = new Company.IT.Developer("Bob", 101, "TypeScript");
8. Module Augmentation
Extend existing modules.
// Original module
// mathUtils.ts
export function add(a: number, b: number): number {
return a + b;
}
// Augmentation
// mathUtils.extension.ts
import * as MathUtils from "./mathUtils";
declare module "./mathUtils" {
export function subtract(a: number, b: number): number;
}
// Implementation
export function subtract(a: number, b: number): number {
return a - b;
}
9. Global Augmentation
Add types to the global scope.
// global.d.ts
declare global {
interface Window {
myCustomProperty: string;
}
var MY_GLOBAL_CONSTANT: string;
}
export {};
// app.ts
window.myCustomProperty = "Hello";
console.log(MY_GLOBAL_CONSTANT);
10. Ambient Modules
Declare types for modules without implementations.
// declarations.d.ts
declare module "my-library" {
export function doSomething(): void;
export default class MyClass {
method(): string;
}
}
// app.ts
import MyClass, { doSomething } from "my-library";
doSomething();
let instance = new MyClass();
11. Module Resolution
Classic Resolution
// import { a } from "./moduleA"
// Looks for:
// 1. ./moduleA.ts
// 2. ./moduleA.d.ts
Node Resolution
// import { a } from "moduleA"
// Looks for:
// 1. node_modules/moduleA.ts
// 2. node_modules/moduleA/package.json (types field)
// 3. node_modules/moduleA/index.ts
// 4. node_modules/@types/moduleA/index.d.ts
Path Mapping
// tsconfig.json
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@models/*": ["models/*"],
"@services/*": ["services/*"],
"@utils/*": ["utils/*"]
}
}
}
// Instead of:
import { User } from "../../../models/user";
// Use:
import { User } from "@models/user";
12. Barrel Pattern
Create index files to simplify imports.
// services/user.ts
export class UserService {}
// services/product.ts
export class ProductService {}
// services/order.ts
export class OrderService {}
// services/index.ts (Barrel)
export * from "./user";
export * from "./product";
export * from "./order";
// app.ts
import { UserService, ProductService, OrderService } from "./services";
13. Dynamic Imports
Load modules dynamically at runtime.
async function loadModule() {
const module = await import("./heavyModule");
module.doSomething();
}
// Conditional loading
if (condition) {
import("./moduleA").then(m => m.execute());
} else {
import("./moduleB").then(m => m.execute());
}
14. CommonJS Interoperability
// ESM import of CommonJS module
import express = require("express");
const app = express();
// Or with esModuleInterop
import express from "express";
const app = express();
15. Real-World Example: Application Structure
src/
├── models/
│ ├── user.ts
│ ├── product.ts
│ └── index.ts
├── services/
│ ├── userService.ts
│ ├── productService.ts
│ └── index.ts
├── utils/
│ ├── validation.ts
│ ├── formatting.ts
│ └── index.ts
├── types/
│ ├── api.ts
│ └── index.ts
└── index.ts
// models/user.ts
export interface User {
id: number;
name: string;
email: string;
}
// models/index.ts
export * from "./user";
export * from "./product";
// services/userService.ts
import { User } from "@models";
export class UserService {
getUser(id: number): User | undefined {
// Implementation
return undefined;
}
}
// services/index.ts
export * from "./userService";
export * from "./productService";
// index.ts
import { UserService } from "@services";
import { User } from "@models";
const userService = new UserService();
Key Takeaways
✅ ES6 modules are the standard for TypeScript
✅ Use named exports for multiple exports
✅ Use default exports for single primary exports
✅ Barrel pattern simplifies imports
✅ Path mapping creates cleaner import paths
✅ Namespaces are legacy; prefer modules
✅ Dynamic imports enable code splitting
Practice Exercises
Exercise 1: Create a Module Structure
utils/
├── string.ts
├── number.ts
├── date.ts
└── index.ts
// string.ts
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
// index.ts
export * from "./string";
export * from "./number";
export * from "./date";
Exercise 2: Path Mapping
Configure path aliases in tsconfig.json and refactor imports.
Exercise 3: Dynamic Import
async function loadPlugin(name: string) {
const plugin = await import(`./plugins/${name}`);
plugin.initialize();
}
Next Steps
In Module 11, we'll explore Decorators for metaprogramming and adding behavior to classes and members.