April 28, 2025

TypeScript Modules and Namespaces

As your TypeScript projects grow, code organization becomes crucial for maintainability and scalability. Fortunately, TypeScript provides powerful tools for structuring your code cleanly: Modules, Namespaces, and Configuration via tsconfig.json.

In this article, we’ll break down how to use ES Modules, Namespaces, and configure your TypeScript project efficiently.

ES Modules (Import/Export)

Modern JavaScript (ES6+) introduced a standardized way to split code into reusable pieces through modules, and TypeScript fully embraces it.

Each file in TypeScript is its own module, and you can explicitly export or import code between files.

Exporting

You can export a function, class, variable, or type:

// utils/math.ts
export function add(a: number, b: number): number {
  return a + b;
}

export const PI = 3.14;

You can also export everything at once:

// utils/math.ts
function subtract(a: number, b: number): number {
  return a - b;
}

function multiply(a: number, b: number): number {
  return a * b;
}

export { subtract, multiply };

Or use default exports:

// utils/area.ts
export default function area(radius: number) {
  return Math.PI * radius * radius;
}

Importing

You can import individual exports:

import { add, PI } from "./utils/math";
console.log(add(2, 3));

Or import a default export:

import area from "./utils/area";
console.log(area(5));

Modules help separate concerns and improve maintainability, and they work naturally in TypeScript thanks to type checking across files.

Namespaces (Organizing Code Internally)

Before ES Modules became standard, TypeScript offered Namespaces to group related code into a single block.

Namespaces are useful for organizing code within the same file or across several compiled files, especially when using TypeScript in environments without module loaders.

Here’s an example:

namespace Geometry {
  export function areaOfCircle(radius: number): number {
    return Math.PI * radius * radius;
  }

  export function perimeterOfCircle(radius: number): number {
    return 2 * Math.PI * radius;
  }
}

console.log(Geometry.areaOfCircle(5));
console.log(Geometry.perimeterOfCircle(5));

Notice:

When to Use Namespaces

Namespaces are less common in modern TypeScript projects where ES Modules are preferred. However, they are still valuable when:

TypeScript Configuration (tsconfig.json)

Managing larger TypeScript projects becomes easier with the right configuration.

The tsconfig.json file defines how the TypeScript compiler (tsc) should behave.

Here’s a basic example:

{
  "compilerOptions": {
    "target": "ES6",
    "module": "ES6",
    "strict": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Key Compiler Options

Why is tsconfig.json important?

Bonus: Setting Up Absolute Imports

As projects grow, relative imports like ../../../components/Button become messy and hard to manage. Absolute imports fix this by letting you import modules relative to a base path.

Here’s how you can set it up:

Step 1: Update tsconfig.json

{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@components/*": ["components/*"],
      "@utils/*": ["utils/*"]
    }
  }
}

Step 2: Use the aliases in your imports

Instead of:

import Button from "../../../components/Button";

You can now write:

import Button from "@components/Button";

Benefits of Absolute Imports:

Note: If you’re using tools like Webpack, Vite, or Next.js, you may also need to configure the resolver separately in those environments to match your TypeScript setup.

Conclusion

Organizing code in TypeScript with ES Modules, Namespaces, and a well-tuned tsconfig.json setup is key to writing scalable, maintainable applications.

Master these tools, and your TypeScript projects will be ready for any scale!