Skip to main content

Modules & Configuration

Understanding how TypeScript handles modules and project configuration is essential for building real-world applications. This chapter covers ES modules, namespaces, and the all-important tsconfig.json.

1. ES Modules

TypeScript fully supports ES modules (ESM), the standard JavaScript module system.

Named Exports

// utils.ts
export const PI = 3.14159;

export function add(a: number, b: number): number {
  return a + b;
}

export function subtract(a: number, b: number): number {
  return a - b;
}

export interface User {
  id: number;
  name: string;
}

export class Calculator {
  add(a: number, b: number): number {
    return a + b;
  }
}
// main.ts
import { PI, add, subtract, User, Calculator } from './utils';

console.log(PI);        // 3.14159
console.log(add(2, 3)); // 5

const user: User = { id: 1, name: 'Alice' };
const calc = new Calculator();

Default Exports

// User.ts
export default class User {
  constructor(public name: string, public email: string) {}

  greet(): string {
    return `Hello, I'm ${this.name}`;
  }
}

// main.ts
import User from './User'; // No braces for default import

const user = new User('Alice', '[email protected]');

Mixed Exports

// api.ts
export const API_URL = 'https://api.example.com';
export const VERSION = '1.0.0';

export default class ApiClient {
  constructor(private baseUrl = API_URL) {}

  async get<T>(path: string): Promise<T> {
    const response = await fetch(`${this.baseUrl}${path}`);
    return response.json();
  }
}

// main.ts
import ApiClient, { API_URL, VERSION } from './api';

Import Aliases

import { add as sum, subtract as minus } from './utils';

console.log(sum(2, 3));   // 5
console.log(minus(5, 2)); // 3

Namespace Import

import * as utils from './utils';

console.log(utils.PI);
console.log(utils.add(2, 3));

Type-Only Imports

// Only import types (removed at runtime)
import type { User, Config } from './types';

// Combined import
import { fetchUser, type User } from './api';

// Type-only export
export type { User, Config };

2. Re-exports (Barrel Files)

Create a single entry point for multiple modules.
// 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 (barrel file)
export { User } from './User';
export { Product } from './Product';
export * from './Order';  // Re-export everything from Order

// main.ts
import { User, Product, Order } from './models';

Selective Re-exports

// services/index.ts
export { UserService } from './UserService';
export { ProductService } from './ProductService';
// OrderService is not exported (internal use only)

// Rename on re-export
export { InternalService as ExternalService } from './InternalService';

3. Module Resolution

How TypeScript finds imported modules.

Relative Imports

// Relative paths - start with ./ or ../
import { utils } from './utils';         // Same directory
import { config } from '../config';      // Parent directory
import { helper } from './lib/helper';   // Subdirectory

Non-relative Imports

// Node modules
import express from 'express';
import { Request, Response } from 'express';

// Path aliases (configured in tsconfig)
import { User } from '@models/User';
import { api } from '@services/api';

Module Resolution Strategies

// tsconfig.json
{
  "compilerOptions": {
    // "node" - Node.js style resolution
    // "classic" - TypeScript's original resolution (rarely used)
    // "node16" / "nodenext" - ESM-aware Node.js resolution
    "moduleResolution": "node"
  }
}

4. Declaration Files (.d.ts)

Type definitions for JavaScript libraries.

Using @types Packages

npm install --save-dev @types/node
npm install --save-dev @types/express
npm install --save-dev @types/lodash

Creating Declaration Files

// types/mylib.d.ts
declare module 'mylib' {
  export function doSomething(value: string): number;
  export const VERSION: string;

  export interface Options {
    debug?: boolean;
    timeout?: number;
  }

  export default class MyLib {
    constructor(options?: Options);
    process(data: string): string;
  }
}

Ambient Declarations

// globals.d.ts
declare const __DEV__: boolean;
declare const __VERSION__: string;

declare function gtag(command: string, ...args: any[]): void;

declare interface Window {
  analytics: {
    track(event: string, data?: object): void;
  };
}

5. Namespaces

TypeScript’s original module system (still useful for type organization).
namespace Validation {
  export interface Validator {
    isValid(value: string): boolean;
  }

  export class EmailValidator implements Validator {
    isValid(value: string): boolean {
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
    }
  }

  export class PhoneValidator implements Validator {
    isValid(value: string): boolean {
      return /^\d{10}$/.test(value);
    }
  }
}

// Usage
const emailValidator = new Validation.EmailValidator();
emailValidator.isValid('[email protected]'); // true

Nested Namespaces

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

  export namespace Services {
    export class UserService {
      getUser(id: number): Models.User | null {
        return null;
      }
    }
  }
}

// Usage
const user: App.Models.User = { id: 1, name: 'Alice' };
const service = new App.Services.UserService();
Prefer ES modules over namespaces for new code. Namespaces are still useful for:
  • Organizing types in declaration files
  • Global type augmentation
  • Legacy codebases

6. tsconfig.json

The TypeScript configuration file. Run tsc --init to generate one.

Essential Options

{
  "compilerOptions": {
    // Target JavaScript version
    "target": "ES2022",

    // Module system for output
    "module": "NodeNext",

    // How to resolve imports
    "moduleResolution": "NodeNext",

    // Output directory
    "outDir": "./dist",

    // Source directory
    "rootDir": "./src",

    // Enable all strict type checks
    "strict": true,

    // Allow importing .json files
    "resolveJsonModule": true,

    // Ensure consistent casing in imports
    "forceConsistentCasingInFileNames": true,

    // Skip type checking of declaration files
    "skipLibCheck": true,

    // Generate .d.ts files
    "declaration": true,

    // Generate source maps
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Strict Mode Options

{
  "compilerOptions": {
    "strict": true,
    // Or individually:
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "useUnknownInCatchVariables": true,
    "alwaysStrict": true
  }
}

Additional Checks

{
  "compilerOptions": {
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true
  }
}

Path Aliases

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@models/*": ["src/models/*"],
      "@services/*": ["src/services/*"],
      "@utils/*": ["src/utils/*"]
    }
  }
}
// With path aliases
import { User } from '@models/User';
import { fetchUser } from '@services/api';
import { formatDate } from '@utils/date';
Path aliases require additional configuration in your bundler (Webpack, Vite) or Node.js (tsconfig-paths package).

7. Project References

Split large projects into smaller, independently compiled pieces.

Main tsconfig.json

{
  "files": [],
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/api" },
    { "path": "./packages/web" }
  ]
}

Package tsconfig.json

// packages/core/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "declaration": true,
    "declarationMap": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"]
}

// packages/api/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "references": [
    { "path": "../core" }
  ],
  "include": ["src/**/*"]
}

Build Command

# Build all projects
tsc --build

# Build with watch
tsc --build --watch

# Clean build
tsc --build --clean

8. Common Configurations

Node.js Backend

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

React Frontend

{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "strict": true,
    "noEmit": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "allowImportingTsExtensions": true
  },
  "include": ["src"]
}

Library

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.test.ts"]
}

9. Module Augmentation

Extend existing modules and types.

Extending Third-Party Types

// Extend Express Request
declare module 'express' {
  interface Request {
    user?: {
      id: string;
      role: 'admin' | 'user';
    };
  }
}

// Now TypeScript knows about req.user
import { Request, Response } from 'express';

function handler(req: Request, res: Response) {
  if (req.user) {
    console.log(req.user.id);
  }
}

Extending Global Types

// Extend Window
declare global {
  interface Window {
    __INITIAL_STATE__: {
      user: User | null;
      config: AppConfig;
    };
  }
}

// Extend Array
declare global {
  interface Array<T> {
    first(): T | undefined;
    last(): T | undefined;
  }
}

Array.prototype.first = function () {
  return this[0];
};

Array.prototype.last = function () {
  return this[this.length - 1];
};

10. Build Tools Integration

package.json Scripts

{
  "scripts": {
    "build": "tsc",
    "build:watch": "tsc --watch",
    "dev": "tsx watch src/index.ts",
    "start": "node dist/index.js",
    "typecheck": "tsc --noEmit",
    "lint": "eslint src --ext .ts",
    "test": "vitest"
  }
}

ESLint Configuration

// eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default tseslint.config(
  eslint.configs.recommended,
  ...tseslint.configs.recommended,
  {
    rules: {
      '@typescript-eslint/no-unused-vars': 'error',
      '@typescript-eslint/no-explicit-any': 'warn',
      '@typescript-eslint/explicit-function-return-type': 'off'
    }
  }
);

Vitest Configuration

// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    include: ['src/**/*.test.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html']
    }
  }
});

Summary

ConceptExample
Named Exportexport { User }
Default Exportexport default class User
Named Importimport { User } from './User'
Default Importimport User from './User'
Type-only Importimport type { User } from './User'
Barrel Fileexport * from './module'
Declaration File.d.ts files
Namespacenamespace App { }
Path Aliases"@models/*": ["src/models/*"]
Project References"references": [{ "path": "./pkg" }]
Module Augmentationdeclare module 'express' { }

What’s Next?

Congratulations! You’ve completed the TypeScript Crash Course. You now have a solid foundation in:
  • ✅ Type annotations and inference
  • ✅ Functions and generics
  • ✅ Interfaces and type aliases
  • ✅ Classes and OOP patterns
  • ✅ Advanced types and utility types
  • ✅ Modules and configuration

Continue Learning

React + TypeScript

Build type-safe React applications with hooks, context, and components.

Node.js + TypeScript

Create robust backend APIs with Express, NestJS, or Fastify.

Full-Stack TypeScript

End-to-end type safety with tRPC, Prisma, and Next.js.

Advanced Patterns

Explore design patterns, dependency injection, and architecture.