Modules & Namespaces#
TypeScript supports modern ES6 modules for organizing code, as well as its own namespace system for legacy scenarios.
ES6 Modules (Recommended)#
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:#
- ✅ Building modern applications
- ✅ Using a bundler (Webpack, Rollup, etc.)
- ✅ Working with npm packages
- ✅ 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:#
- ✅ Working with global scripts (no module system)
- ✅ Organizing code in a single file
- ✅ Maintaining legacy TypeScript code
- ✅ 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#
-
✅ Use ES6 modules over namespaces
// ✅ Preferred export class UserService {} // ❌ Legacy namespace Services { export class UserService {} } -
✅ Use barrel exports for cleaner imports
// utils/index.ts export * from "./date"; export * from "./validation"; // Clean import import { formatDate, validateEmail } from "./utils"; -
✅ Use path aliases for deep imports
import { User } from "@/models/user"; // Instead of: "../../../models/user" -
✅ Split type definitions from implementations
// types.ts export interface User { /* ... */ } // user.service.ts import type { User } from "./types"; -
✅ Avoid circular dependencies
// Use type-only imports when necessary import type { Post } from "./post"; -
❌ 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! 🎉
- Review Basic Types for foundational concepts
- Practice with Functions and Classes
- Build projects using Generics and Utility Types
Keep coding and enjoy TypeScript's type safety! 🚀