Back to Blog
TypeScriptNamespacesModulesOrganization

TypeScript Namespaces vs Modules

Understand TypeScript namespaces and modules for organizing and structuring code.

B
Bootspring Team
Engineering
December 10, 2018
6 min read

TypeScript provides namespaces and modules for code organization. Here's when and how to use each.

1// math.ts - Export functions 2export function add(a: number, b: number): number { 3 return a + b; 4} 5 6export function subtract(a: number, b: number): number { 7 return a - b; 8} 9 10export const PI = 3.14159; 11 12// Default export 13export default class Calculator { 14 calculate(expression: string): number { 15 // Implementation 16 return 0; 17 } 18} 19 20// main.ts - Import and use 21import Calculator, { add, subtract, PI } from './math'; 22import * as math from './math'; 23 24console.log(add(2, 3)); // 5 25console.log(math.subtract(5, 2)); // 3 26 27const calc = new Calculator();

Re-exporting#

1// shapes/circle.ts 2export interface Circle { 3 radius: number; 4} 5 6export function area(circle: Circle): number { 7 return Math.PI * circle.radius ** 2; 8} 9 10// shapes/rectangle.ts 11export interface Rectangle { 12 width: number; 13 height: number; 14} 15 16export function area(rect: Rectangle): number { 17 return rect.width * rect.height; 18} 19 20// shapes/index.ts - Barrel file 21export * from './circle'; 22export * as rectangle from './rectangle'; 23export { area as circleArea } from './circle'; 24export { area as rectangleArea } from './rectangle'; 25 26// main.ts 27import { Circle, circleArea, rectangle } from './shapes'; 28 29const circle: Circle = { radius: 5 }; 30console.log(circleArea(circle)); 31console.log(rectangle.area({ width: 4, height: 3 }));

Namespaces (Legacy Pattern)#

1// Namespaces group related code 2namespace Validation { 3 export interface StringValidator { 4 isValid(s: string): boolean; 5 } 6 7 export class EmailValidator implements StringValidator { 8 isValid(s: string): boolean { 9 return s.includes('@'); 10 } 11 } 12 13 export class ZipCodeValidator implements StringValidator { 14 isValid(s: string): boolean { 15 return /^\d{5}$/.test(s); 16 } 17 } 18 19 // Internal helper (not exported) 20 function sanitize(s: string): string { 21 return s.trim().toLowerCase(); 22 } 23} 24 25// Usage 26const emailValidator = new Validation.EmailValidator(); 27console.log(emailValidator.isValid('test@example.com'));

Nested Namespaces#

1namespace App { 2 export namespace Models { 3 export interface User { 4 id: number; 5 name: string; 6 } 7 8 export interface Post { 9 id: number; 10 title: string; 11 authorId: number; 12 } 13 } 14 15 export namespace Services { 16 export class UserService { 17 getUser(id: number): Models.User { 18 return { id, name: 'John' }; 19 } 20 } 21 22 export class PostService { 23 getPosts(userId: number): Models.Post[] { 24 return []; 25 } 26 } 27 } 28} 29 30// Usage 31const userService = new App.Services.UserService(); 32const user: App.Models.User = userService.getUser(1);

Declaration Merging#

1// Namespaces can merge with classes, functions, and enums 2 3// Class + Namespace 4class Album { 5 label: Album.AlbumLabel; 6} 7 8namespace Album { 9 export interface AlbumLabel { 10 name: string; 11 } 12} 13 14// Function + Namespace 15function buildLabel(name: string): string { 16 return buildLabel.prefix + name; 17} 18 19namespace buildLabel { 20 export let prefix = 'Hello, '; 21} 22 23console.log(buildLabel('World')); // 'Hello, World' 24 25// Enum + Namespace 26enum Color { 27 Red = 1, 28 Green = 2, 29 Blue = 4, 30} 31 32namespace Color { 33 export function mix(c1: Color, c2: Color): Color { 34 return c1 | c2; 35 } 36} 37 38console.log(Color.mix(Color.Red, Color.Blue)); // 5

Ambient Namespaces#

1// global.d.ts - Declare external library 2declare namespace JQuery { 3 interface AjaxSettings { 4 url: string; 5 type?: string; 6 data?: any; 7 } 8 9 interface JQueryStatic { 10 ajax(settings: AjaxSettings): void; 11 (selector: string): JQueryElement; 12 } 13 14 interface JQueryElement { 15 html(): string; 16 html(content: string): JQueryElement; 17 click(handler: () => void): JQueryElement; 18 } 19} 20 21declare const $: JQuery.JQueryStatic; 22 23// Usage (no import needed) 24$.ajax({ url: '/api/data' }); 25$('#button').click(() => console.log('Clicked'));

Module Augmentation#

1// Extend existing module 2import { Observable } from 'rxjs'; 3 4// Augment the module 5declare module 'rxjs' { 6 interface Observable<T> { 7 customMethod(): Observable<T>; 8 } 9} 10 11// Implement the augmentation 12Observable.prototype.customMethod = function () { 13 return this; 14}; 15 16// Now available on all Observables 17import { of } from 'rxjs'; 18of(1, 2, 3).customMethod();

Global Augmentation#

1// Add to global scope 2declare global { 3 interface Window { 4 myApp: { 5 version: string; 6 init(): void; 7 }; 8 } 9 10 interface Array<T> { 11 first(): T | undefined; 12 last(): T | undefined; 13 } 14} 15 16// Implement 17Array.prototype.first = function () { 18 return this[0]; 19}; 20 21Array.prototype.last = function () { 22 return this[this.length - 1]; 23}; 24 25// Usage 26window.myApp = { 27 version: '1.0.0', 28 init() { 29 console.log('App initialized'); 30 }, 31}; 32 33[1, 2, 3].first(); // 1 34[1, 2, 3].last(); // 3 35 36export {}; // Make this a module

When to Use What#

1// USE MODULES (ES Modules) when: 2// - Modern projects 3// - Node.js or bundled browser code 4// - Need tree-shaking 5// - Working with npm packages 6 7// math.ts 8export const add = (a: number, b: number) => a + b; 9 10// main.ts 11import { add } from './math'; 12 13// USE NAMESPACES when: 14// - Organizing global declarations 15// - Augmenting existing types 16// - Legacy codebases 17// - Browser scripts without bundler 18 19namespace MyApp { 20 export function init() {} 21}

Module Organization#

1// Recommended project structure 2 3// src/ 4// models/ 5// index.ts 6// user.ts 7// post.ts 8// services/ 9// index.ts 10// user.service.ts 11// post.service.ts 12// utils/ 13// index.ts 14// format.ts 15// validate.ts 16// index.ts 17 18// src/models/user.ts 19export interface User { 20 id: number; 21 name: string; 22 email: string; 23} 24 25// src/models/index.ts 26export * from './user'; 27export * from './post'; 28 29// src/services/user.service.ts 30import type { User } from '../models'; 31 32export class UserService { 33 async getUser(id: number): Promise<User> { 34 // Implementation 35 } 36} 37 38// src/index.ts 39export * from './models'; 40export * from './services'; 41export * from './utils'; 42 43// Consumer 44import { User, UserService } from './src';

Best Practices#

Modules: ✓ Use ES modules for new code ✓ Use barrel files (index.ts) ✓ Import types with 'import type' ✓ Organize by feature/domain Namespaces: ✓ Use for global declarations ✓ Use for type augmentation ✓ Keep flat when possible ✓ Document clearly Avoid: ✗ Mixing modules and namespaces ✗ Deep namespace nesting ✗ Namespaces in modern apps ✗ Circular dependencies Migration: ✓ Convert namespaces to modules ✓ Use barrel files for grouping ✓ Keep ambient declarations ✓ Update imports gradually

Conclusion#

ES Modules are the standard for modern TypeScript. Use them for code organization, tree-shaking, and npm package compatibility. Reserve namespaces for global declarations, type augmentation, and legacy codebases. When migrating, convert namespaces to modules using barrel files for grouping related exports.

Share this article

Help spread the word about Bootspring