Skip to main content
Advertisement

8.3 Module Augmentation — Extending Existing Types

What is Module Augmentation?

Module Augmentation is a technique to modify or extend type definitions of existing modules without touching the library source code.

// Adding user info types to the existing express module
import 'express';

declare module 'express' {
interface Request {
user?: AuthUser; // Adding user property to existing Request
sessionId?: string;
}
}

Why Module Augmentation is Needed

The Problem

import express, { Request, Response } from 'express';

// Implementing JWT middleware
function authMiddleware(req: Request, res: Response, next: Function) {
const token = req.headers.authorization;
const user = verifyToken(token);
req.user = user; // ❌ Property 'user' does not exist on type 'Request'
next();
}

Solution: Module Augmentation

// src/types/express.d.ts
import { AuthUser } from '../models/user';

declare module 'express-serve-static-core' {
interface Request {
user?: AuthUser;
}
}

// No more type errors
function authMiddleware(req: Request, res: Response, next: Function) {
req.user = verifyToken(req.headers.authorization); // ✅
next();
}

Basic Syntax

External Module Augmentation

// Must have an import to be recognized as a module file
import type { SomeType } from 'some-library';

declare module 'some-library' {
// Extend existing interface
interface ExistingInterface {
newProperty: string;
}

// Add new function
function newFunction(arg: string): void;
}

Interface Merging (Declaration Merging)

TypeScript automatically merges multiple declarations of the same interface name.

// Library's original definition
interface Window {
location: Location;
history: History;
}

// Our additions
interface Window {
myPlugin: MyPlugin;
analytics: Analytics;
}

// TypeScript merges both declarations
// Window = { location, history, myPlugin, analytics }

Real-World Examples

Example 1: Express Request Extension

// src/types/express-ext.d.ts
import type { User } from '../models/user';
import type { Logger } from '../utils/logger';

declare module 'express-serve-static-core' {
interface Request {
user?: User;
logger: Logger;
startTime: number;
requestId: string;
}
}
// src/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';

export function authMiddleware(req: Request, res: Response, next: NextFunction) {
try {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) throw new Error('No token');

req.user = verifyJwt(token); // ✅ Type safe
req.requestId = generateId(); // ✅
next();
} catch {
res.status(401).json({ error: 'Unauthorized' });
}
}

Example 2: Extending the Global Window Object

// src/types/global.d.ts
interface Window {
// Google Analytics
gtag: (command: string, id: string, params?: Record<string, unknown>) => void;

// App configuration
__APP_CONFIG__: {
apiUrl: string;
version: string;
featureFlags: Record<string, boolean>;
};

// Stripe
Stripe?: (key: string) => StripeInstance;
}
// Usage
window.gtag('event', 'page_view', { page_path: '/home' }); // ✅

const config = window.__APP_CONFIG__;
console.log(config.version); // ✅

Example 3: Extending Library Interfaces

// Fastify plugin type extension
import 'fastify';

declare module 'fastify' {
interface FastifyInstance {
db: DatabaseClient;
redis: RedisClient;
jwt: JwtHelper;
}

interface FastifyRequest {
user?: AuthUser;
session: SessionData;
}
}
// Fastify plugin implementation
import fp from 'fastify-plugin';

export default fp(async (fastify) => {
const db = await createDatabaseClient();
fastify.decorate('db', db); // ✅ fastify.db is type safe
});

// Usage in routes
fastify.get('/users', async (request, reply) => {
const users = await request.server.db.query('SELECT * FROM users'); // ✅
return users;
});

Example 4: process.env Type Definitions

// src/types/env.d.ts
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
DATABASE_URL: string;
JWT_SECRET: string;
PORT?: string;
API_KEY?: string;
}
}
// Type-safe usage
const dbUrl: string = process.env.DATABASE_URL; // ✅ string (not undefined)
const port = parseInt(process.env.PORT ?? '3000', 10); // ✅

// ❌ Typos caught immediately
process.env.TYPO_KEY; // Property 'TYPO_KEY' does not exist

Global Type Declarations

Global declarations in script files (without import/export).

// src/types/global.d.ts (no import/export)

// Global type declarations
type ID = string | number;
type Nullable<T> = T | null;
type Optional<T> = T | undefined;

// Global interface
interface Pagination {
page: number;
limit: number;
total: number;
}

// Global function
declare function log(message: string, level?: 'info' | 'warn' | 'error'): void;
// Usable anywhere without import
const id: ID = '123'; // ✅
const page: Pagination = { page: 1, limit: 10, total: 100 }; // ✅

Note: Files with import or export are treated as modules. Use declare global {} for global declarations.

// src/types/augment.d.ts (global declaration even with imports)
import type { User } from './user';

// Global declarations go inside declare global
declare global {
interface Window {
currentUser: User;
}

type ID = string | number;
}

export {}; // Empty export to make it a module file

Module Augmentation Caveats

1. Verify the Correct Module Name

// For Express, the actual module name is different!
declare module 'express' { } // ❌ Doesn't work
declare module 'express-serve-static-core' { } // ✅ Actual interface location

How to check:

# Open and check node_modules/@types/express/index.d.ts
cat node_modules/@types/express/index.d.ts

2. Cannot Add Completely New Modules

// Can only augment existing modules
declare module 'existing-lib' {
interface NewInterface { } // ✅ New interface can be added
function existingFn(): void; // ✅ Overload existing function
}

3. Ensure Inclusion in tsconfig

{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./src/types"]
},
"include": ["src/**/*.ts", "src/types/**/*.d.ts"]
}

Pro Tips

Finding the correct augmentation target for each library

# Find interface names in @types package's index.d.ts
grep -r "interface Request" node_modules/@types/express/
# → Found in express-serve-static-core module

grep -r "interface FastifyInstance" node_modules/@types/fastify/

Recommended type augmentation file structure

src/
└── types/
├── express.d.ts # Express extensions
├── env.d.ts # process.env types
├── global.d.ts # Global types
└── window.d.ts # window object extensions

Create reusable augmentation packages

Extract commonly used module augmentations into npm packages for reuse across multiple projects.

Advertisement