Skip to main content

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");
When to Use Default Exports

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.