Skip to content

Modules & Namespaces#

TypeScript supports modern ES6 modules for organizing code, as well as its own namespace system for legacy scenarios.

Exporting#

// user.ts

// Named exports
export interface User {
  id: number;
  name: string;
  email: string;
}

export function createUser(name: string, email: string): User {
  return {
    id: Math.random(),
    name,
    email
  };
}

export const DEFAULT_ROLE = "user";

// Export existing declarations
function validateEmail(email: string): boolean {
  return email.includes("@");
}

export { validateEmail };

// Export with alias
export { validateEmail as checkEmail };

Default Exports#

// database.ts

// Default export (one per module)
export default class Database {
  connect(): void {
    console.log("Connected to database");
  }
}

// Or
class Database {
  connect(): void {
    console.log("Connected to database");
  }
}

export default Database;

// Can combine default and named exports
export const DEFAULT_TIMEOUT = 5000;
export default Database;

Importing#

// app.ts

// Named imports
import { User, createUser, DEFAULT_ROLE } from "./user";

const user = createUser("Alice", "alice@example.com");

// Import with alias
import { validateEmail as checkEmail } from "./user";

checkEmail("test@example.com");

// Import all as namespace
import * as UserModule from "./user";

const user2 = UserModule.createUser("Bob", "bob@example.com");
console.log(UserModule.DEFAULT_ROLE);

// Default import
import Database from "./database";

const db = new Database();
db.connect();

// Import default with named imports
import Database, { DEFAULT_TIMEOUT } from "./database";

// Import for side effects only
import "./polyfills";

Re-exporting#

// models/index.ts

// Re-export all from a module
export * from "./user";
export * from "./post";
export * from "./comment";

// Re-export specific items
export { User, createUser } from "./user";
export { Post } from "./post";

// Re-export with alias
export { User as UserModel } from "./user";

// Re-export default as named
export { default as Database } from "./database";

Module Resolution#

Relative Imports#

// From ./src/app.ts

import { User } from "./models/user";        // ./src/models/user.ts
import { Database } from "../database";      // ./database.ts
import * as utils from "./utils/";           // ./src/utils/index.ts

Non-relative Imports#

// Import from node_modules
import { Component } from "react";
import express from "express";

// Type-only imports
import type { User } from "./types";

Path Mapping (tsconfig.json)#

{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@/models/*": ["models/*"],
      "@/utils/*": ["utils/*"],
      "@/components/*": ["components/*"]
    }
  }
}
// Now you can import like this:
import { User } from "@/models/user";
import { formatDate } from "@/utils/date";

Namespaces (Legacy)#

Used mainly for organizing code in older TypeScript projects or when not using modules.

Basic Namespace#

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://") || url.startsWith("https://");
    }
  }
}

// Usage
const emailValidator = new Validation.EmailValidator();
console.log(emailValidator.isValid("test@example.com"));

Nested Namespaces#

namespace App {
  export namespace Models {
    export interface User {
      id: number;
      name: string;
    }

    export interface Post {
      id: number;
      title: string;
      userId: number;
    }
  }

  export namespace Services {
    export class UserService {
      getUser(id: number): Models.User {
        return { id, name: "Alice" };
      }
    }
  }
}

// Usage
const userService = new App.Services.UserService();
const user: App.Models.User = userService.getUser(1);

Namespace Aliases#

namespace Shapes {
  export namespace Polygons {
    export class Triangle {
      // ...
    }

    export class Square {
      // ...
    }
  }
}

// Alias for convenience
import Polygons = Shapes.Polygons;

const square = new Polygons.Square();

Module vs Namespace#

Use Modules When:#

  1. ✅ Building modern applications
  2. ✅ Using a bundler (Webpack, Rollup, etc.)
  3. ✅ Working with npm packages
  4. ✅ Want tree-shaking and code splitting
// Recommended: ES6 modules
// user-service.ts
export class UserService {
  getUser(id: number): User {
    // ...
  }
}

// app.ts
import { UserService } from "./user-service";

Use Namespaces When:#

  1. ✅ Working with global scripts (no module system)
  2. ✅ Organizing code in a single file
  3. ✅ Maintaining legacy TypeScript code
  4. ✅ Writing declaration files (.d.ts)
// Namespaces for global scripts
namespace MyLib {
  export function doSomething(): void {
    // ...
  }
}

// Available globally
MyLib.doSomething();

Practical Examples#

Feature-based Module Structure#

// features/user/types.ts
export interface User {
  id: number;
  name: string;
  email: string;
}

export interface CreateUserDto {
  name: string;
  email: string;
}

// features/user/service.ts
import { User, CreateUserDto } from "./types";

export class UserService {
  private users: User[] = [];

  create(dto: CreateUserDto): User {
    const user: User = {
      id: this.users.length + 1,
      ...dto
    };
    this.users.push(user);
    return user;
  }

  findById(id: number): User | undefined {
    return this.users.find(u => u.id === id);
  }
}

// features/user/index.ts
export * from "./types";
export * from "./service";

// app.ts
import { UserService, CreateUserDto } from "./features/user";

const service = new UserService();
const user = service.create({ name: "Alice", email: "alice@example.com" });

Barrel Exports#

// utils/index.ts
export { formatDate, parseDate } from "./date";
export { validateEmail, validatePhone } from "./validation";
export { capitalize, truncate } from "./string";

// Now import from a single place
import { formatDate, validateEmail, capitalize } from "./utils";

Dependency Injection#

// services/database.ts
export interface Database {
  connect(): Promise<void>;
  disconnect(): Promise<void>;
}

export class PostgresDatabase implements Database {
  async connect(): Promise<void> {
    console.log("Connected to PostgreSQL");
  }

  async disconnect(): Promise<void> {
    console.log("Disconnected from PostgreSQL");
  }
}

// services/user-repository.ts
import { Database } from "./database";
import { User } from "../models/user";

export class UserRepository {
  constructor(private db: Database) {}

  async findAll(): Promise<User[]> {
    await this.db.connect();
    // Query users
    return [];
  }
}

// app.ts
import { PostgresDatabase } from "./services/database";
import { UserRepository } from "./services/user-repository";

const db = new PostgresDatabase();
const userRepo = new UserRepository(db);

Module Augmentation#

Extend existing modules with new functionality:

// Extending Express Request
import { Request } from "express";

declare module "express" {
  interface Request {
    user?: {
      id: number;
      name: string;
    };
  }
}

// Now you can use it
import { Request, Response } from "express";

app.get("/profile", (req: Request, res: Response) => {
  if (req.user) {
    res.json(req.user);
  }
});

Global Type Declarations#

// types/global.d.ts

// Global type
declare global {
  interface Window {
    myCustomProperty: string;
  }

  const API_URL: string;
  const VERSION: string;
}

// Must export something to make it a module
export {};

// Now available everywhere
window.myCustomProperty = "value";
console.log(API_URL);

Library Structure#

// lib/index.ts (main entry point)
export { EventEmitter } from "./event-emitter";
export { Logger } from "./logger";
export { HttpClient } from "./http-client";

export type { EventHandler } from "./event-emitter";
export type { LogLevel } from "./logger";
export type { RequestConfig } from "./http-client";

// lib/event-emitter.ts
export type EventHandler<T = any> = (data: T) => void;

export class EventEmitter {
  // Implementation
}

// lib/logger.ts
export type LogLevel = "debug" | "info" | "warn" | "error";

export class Logger {
  // Implementation
}

// Usage
import { EventEmitter, Logger } from "my-library";
import type { LogLevel } from "my-library";

Circular Dependency Prevention#

// ❌ Circular dependency
// user.ts
import { Post } from "./post";

export interface User {
  id: number;
  posts: Post[];
}

// post.ts
import { User } from "./user";

export interface Post {
  id: number;
  author: User;
}

// ✅ Solution 1: Use types in a separate file
// types.ts
export interface User {
  id: number;
  posts: Post[];
}

export interface Post {
  id: number;
  author: User;
}

// user.ts
import type { User, Post } from "./types";

export class UserService {
  getUser(id: number): User {
    // ...
  }
}

// ✅ Solution 2: Use type-only import
// user.ts
import type { Post } from "./post";

export interface User {
  id: number;
  posts: Post[];
}

// post.ts
import type { User } from "./user";

export interface Post {
  id: number;
  author: User;
}

Code Organization Patterns#

Layer-based Architecture#

src/
├── models/
│   ├── user.ts
│   ├── post.ts
│   └── index.ts
├── repositories/
│   ├── user-repository.ts
│   ├── post-repository.ts
│   └── index.ts
├── services/
│   ├── user-service.ts
│   ├── post-service.ts
│   └── index.ts
├── controllers/
│   ├── user-controller.ts
│   ├── post-controller.ts
│   └── index.ts
└── index.ts

Feature-based Architecture#

src/
├── features/
│   ├── auth/
│   │   ├── auth.service.ts
│   │   ├── auth.controller.ts
│   │   ├── auth.types.ts
│   │   └── index.ts
│   ├── users/
│   │   ├── user.service.ts
│   │   ├── user.controller.ts
│   │   ├── user.types.ts
│   │   └── index.ts
│   └── posts/
│       ├── post.service.ts
│       ├── post.controller.ts
│       ├── post.types.ts
│       └── index.ts
├── shared/
│   ├── types/
│   ├── utils/
│   └── index.ts
└── index.ts

Best Practices#

  1. Use ES6 modules over namespaces

    // ✅ Preferred
    export class UserService {}
    
    // ❌ Legacy
    namespace Services {
      export class UserService {}
    }
    

  2. Use barrel exports for cleaner imports

    // utils/index.ts
    export * from "./date";
    export * from "./validation";
    
    // Clean import
    import { formatDate, validateEmail } from "./utils";
    

  3. Use path aliases for deep imports

    import { User } from "@/models/user";
    // Instead of: "../../../models/user"
    

  4. Split type definitions from implementations

    // types.ts
    export interface User { /* ... */ }
    
    // user.service.ts
    import type { User } from "./types";
    

  5. Avoid circular dependencies

    // Use type-only imports when necessary
    import type { Post } from "./post";
    

  6. Don't mix default and named exports carelessly

    // ❌ Confusing
    export default class User {}
    export const createUser = () => {};
    
    // ✅ Better - be consistent
    export class User {}
    export const createUser = () => {};
    

Next Steps#

You've completed the TypeScript 80-20 Guide! 🎉

Keep coding and enjoy TypeScript's type safety! 🚀