Skip to main content

Overview

StatusFlow is built with TypeScript and provides full type safety for error handling, responses, and middleware. This guide covers TypeScript-specific features, type imports, and best practices.

Type Safety Benefits

Type-Safe Exceptions

Exception classes with typed constructors and properties

Typed Responses

Strongly typed response interfaces and utility functions

Generic Support

Generic types for custom response data

Autocomplete

Full IDE autocomplete for all APIs

TypeScript Setup

Installation

StatusFlow includes TypeScript definitions out of the box:
npm install status-flow express
npm install --save-dev @types/express @types/node typescript

tsconfig.json Configuration

Recommended TypeScript configuration:
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "moduleResolution": "node",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
Enable strict mode to get the full benefits of TypeScript’s type checking with StatusFlow.

Type Imports

StatusFlow exports all necessary types for type-safe development:

Core Types

import type {
  StatusFlowOptions,
  StatusFlowLang,
} from 'status-flow';

Exception Classes

import {
  HttpException,
  BadRequestException,
  UnauthorizedException,
  ForbiddenException,
  NotFoundException,
  ConflictException,
  InternalServerErrorException,
} from 'status-flow';

Response Types

import type { HttpResponse } from 'status-flow';
import { createSuccessResponse, createHttpResponse } from 'status-flow';

Middleware Types

import { statusFlowMiddleware, httpErrorMiddleware } from 'status-flow';
import type { Request, Response, NextFunction } from 'express';

Type-Safe Exception Handling

Using Exception Classes

Exception classes provide type safety for status codes and messages:
import { NotFoundException, BadRequestException } from 'status-flow';
import type { Request, Response } from 'express';

interface User {
  id: string;
  name: string;
  email: string;
}

app.get('/users/:id', (req: Request, res: Response) => {
  const userId = req.params.id;
  
  if (!userId) {
    // TypeScript knows this is a 400 error
    throw new BadRequestException('User ID is required');
  }
  
  const user: User | undefined = database.findUser(userId);
  
  if (!user) {
    // TypeScript knows this is a 404 error
    throw new NotFoundException('User not found', { userId });
  }
  
  // TypeScript ensures user is defined here
  res.json({ success: true, data: user });
});

Custom Exception Details with Types

Define interfaces for exception details:
import { NotFoundException, BadRequestException } from 'status-flow';

interface ValidationErrorDetail {
  field: string;
  message: string;
  value: unknown;
  constraint: string;
}

interface NotFoundDetail {
  resourceType: string;
  resourceId: string | number;
  searchedAt: Date;
}

app.post('/users', (req: Request, res: Response) => {
  const { email, age } = req.body;
  
  if (!email || typeof email !== 'string') {
    const detail: ValidationErrorDetail = {
      field: 'email',
      message: 'Email must be a non-empty string',
      value: email,
      constraint: 'required|string',
    };
    
    throw new BadRequestException('Validation failed', detail);
  }
  
  const existingUser = database.findByEmail(email);
  
  if (existingUser) {
    const detail: NotFoundDetail = {
      resourceType: 'user',
      resourceId: existingUser.id,
      searchedAt: new Date(),
    };
    
    throw new ConflictException('User already exists', detail);
  }
  
  // Create user...
});

Type-Safe StatusFlow Function

Using StatusFlowOptions

The StatusFlow function accepts a typed options object:
import { StatusFlow } from 'status-flow';
import type { StatusFlowOptions, StatusFlowLang } from 'status-flow';

function createErrorResponse(
  code: number,
  lang: StatusFlowLang,
  message?: string,
  extraData?: Record<string, unknown>
) {
  const options: StatusFlowOptions = {
    code,
    lang,
    overrideMessage: message,
    extra: extraData,
  };
  
  return StatusFlow(options);
}

app.get('/error', (req: Request, res: Response) => {
  const lang: StatusFlowLang = req.headers['x-lang'] === 'en' ? 'en' : 'es';
  
  const response = createErrorResponse(
    404,
    lang,
    'Resource not found',
    { timestamp: Date.now() }
  );
  
  res.status(404).json(response);
});

Type-Safe Language Handling

import type { StatusFlowLang } from 'status-flow';

function getLanguageFromRequest(req: Request): StatusFlowLang {
  const langHeader = req.headers['x-lang'];
  
  // TypeScript ensures we return 'es' | 'en'
  if (langHeader === 'en') return 'en';
  return 'es'; // Default
}

app.get('/status', (req: Request, res: Response) => {
  const lang = getLanguageFromRequest(req);
  const response = StatusFlow({ code: 200, lang });
  res.json(response);
});

Generic Response Types

Typed Success Responses

Use generics for type-safe success responses:
import { createSuccessResponse } from 'status-flow';
import type { HttpResponse } from 'status-flow';

interface User {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
}

interface Product {
  id: number;
  name: string;
  price: number;
  inStock: boolean;
}

app.get('/users/:id', async (req: Request, res: Response) => {
  const user: User = await database.getUser(req.params.id);
  
  // TypeScript knows the response includes User data
  const response = createSuccessResponse<User>(
    user,
    'User retrieved successfully'
  );
  
  res.json(response);
});

app.get('/products', async (req: Request, res: Response) => {
  const products: Product[] = await database.getProducts();
  
  // TypeScript knows the response includes Product[] data
  const response = createSuccessResponse<Product[]>(
    products,
    'Products retrieved successfully'
  );
  
  res.json(response);
});

Custom Generic Response Wrapper

Create your own type-safe response wrapper:
import type { HttpResponse } from 'status-flow';

interface ApiResponse<T> extends HttpResponse {
  data?: T;
  timestamp: string;
  requestId: string;
}

function createApiResponse<T>(
  data: T,
  message: string,
  status = 200,
  requestId: string
): ApiResponse<T> {
  return {
    status,
    message,
    data,
    timestamp: new Date().toISOString(),
    requestId,
  };
}

interface PaginatedData<T> {
  items: T[];
  total: number;
  page: number;
  limit: number;
}

app.get('/users', async (req: Request, res: Response) => {
  const page = parseInt(req.query.page as string) || 1;
  const limit = parseInt(req.query.limit as string) || 10;
  
  const { users, total } = await database.getUsersPaginated(page, limit);
  
  // TypeScript knows the exact shape of the response
  const response = createApiResponse<PaginatedData<User>>(
    { items: users, total, page, limit },
    'Users retrieved',
    200,
    req.id
  );
  
  res.json(response);
});

Type-Safe Middleware

Typed Error Middleware

import { Request, Response, NextFunction } from 'express';
import { HttpException } from 'status-flow';
import type { HttpResponse } from 'status-flow';

function customErrorMiddleware(
  err: unknown,
  req: Request,
  res: Response,
  next: NextFunction
): void {
  // Type guard for HttpException
  if (err instanceof HttpException) {
    const response: HttpResponse = {
      status: err.status,
      message: err.message,
      details: err.details,
    };
    
    res.status(err.status).json(response);
    return;
  }
  
  // Handle other error types
  const response: HttpResponse = {
    status: 500,
    message: 'Internal Server Error',
  };
  
  res.status(500).json(response);
}

app.use(customErrorMiddleware);

Type Guards

Create type guards for runtime type checking:
import { HttpException } from 'status-flow';

function isHttpException(error: unknown): error is HttpException {
  return error instanceof HttpException;
}

function hasStatusCode(error: unknown): error is { status: number } {
  return (
    typeof error === 'object' &&
    error !== null &&
    'status' in error &&
    typeof (error as any).status === 'number'
  );
}

app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
  if (isHttpException(err)) {
    // TypeScript knows err is HttpException
    res.status(err.status).json({
      status: err.status,
      message: err.message,
      details: err.details,
    });
    return;
  }
  
  if (hasStatusCode(err)) {
    // TypeScript knows err has status property
    res.status(err.status).json({ error: 'An error occurred' });
    return;
  }
  
  res.status(500).json({ error: 'Internal Server Error' });
});

Typed Route Handlers

Custom Request Types

Extend Express Request with custom properties:
import { Request } from 'express';

interface AuthenticatedRequest extends Request {
  user: {
    id: string;
    email: string;
    role: 'admin' | 'user';
  };
}

function requireAuth(
  req: Request,
  res: Response,
  next: NextFunction
): asserts req is AuthenticatedRequest {
  const token = req.headers.authorization?.replace('Bearer ', '');
  
  if (!token) {
    throw new UnauthorizedException('Authentication required');
  }
  
  const user = verifyToken(token);
  
  if (!user) {
    throw new UnauthorizedException('Invalid token');
  }
  
  (req as AuthenticatedRequest).user = user;
  next();
}

app.get('/profile', requireAuth, (req: Request, res: Response) => {
  // TypeScript knows req is AuthenticatedRequest after requireAuth
  const authReq = req as AuthenticatedRequest;
  
  res.json({
    success: true,
    data: authReq.user,
  });
});

Typed Request Parameters

import { Request, Response } from 'express';
import { NotFoundException } from 'status-flow';

interface UserParams {
  id: string;
}

interface UserQuery {
  include?: 'posts' | 'comments';
}

interface CreateUserBody {
  name: string;
  email: string;
  password: string;
}

type UserRequest = Request<UserParams, {}, {}, UserQuery>;
type CreateUserRequest = Request<{}, {}, CreateUserBody>;

app.get('/users/:id', (req: UserRequest, res: Response) => {
  const { id } = req.params; // TypeScript knows id is string
  const { include } = req.query; // TypeScript knows include is 'posts' | 'comments' | undefined
  
  const user = database.findUser(id);
  
  if (!user) {
    throw new NotFoundException('User not found');
  }
  
  res.json({ success: true, data: user });
});

app.post('/users', (req: CreateUserRequest, res: Response) => {
  const { name, email, password } = req.body; // All properly typed
  
  if (!name || !email || !password) {
    throw new BadRequestException('Missing required fields');
  }
  
  const user = database.createUser({ name, email, password });
  res.status(201).json({ success: true, data: user });
});

Advanced TypeScript Patterns

Result Type Pattern

Implement a Result type for safer error handling:
import { HttpException } from 'status-flow';

type Result<T, E = HttpException> =
  | { success: true; data: T }
  | { success: false; error: E };

function findUser(id: string): Result<User> {
  const user = database.findUser(id);
  
  if (!user) {
    return {
      success: false,
      error: new NotFoundException('User not found', { userId: id }),
    };
  }
  
  return { success: true, data: user };
}

app.get('/users/:id', (req: Request, res: Response) => {
  const result = findUser(req.params.id);
  
  if (!result.success) {
    throw result.error;
  }
  
  // TypeScript knows result.data is User
  res.json({ success: true, data: result.data });
});

Branded Types for IDs

type UserId = string & { readonly __brand: 'UserId' };
type ProductId = number & { readonly __brand: 'ProductId' };

function createUserId(id: string): UserId {
  return id as UserId;
}

function createProductId(id: number): ProductId {
  return id as ProductId;
}

function getUser(id: UserId): User | undefined {
  return database.findUser(id);
}

app.get('/users/:id', (req: Request, res: Response) => {
  const userId = createUserId(req.params.id);
  const user = getUser(userId);
  
  if (!user) {
    throw new NotFoundException('User not found');
  }
  
  res.json({ success: true, data: user });
});

Best Practices

Always use TypeScript’s strict mode for maximum type safety:
{
  "compilerOptions": {
    "strict": true
  }
}
Import types with the type keyword for better tree-shaking:
import type { StatusFlowOptions, StatusFlowLang } from 'status-flow';
import { StatusFlow } from 'status-flow';
Create interfaces for request bodies, params, and responses:
interface CreateUserRequest {
  name: string;
  email: string;
  password: string;
}

interface UserResponse {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
}
Create generic utility functions for common patterns:
function findOrThrow<T>(
  item: T | undefined,
  errorMessage: string
): T {
  if (!item) {
    throw new NotFoundException(errorMessage);
  }
  return item;
}
Use type guards for runtime type checking:
function isValidEmail(value: unknown): value is string {
  return typeof value === 'string' && value.includes('@');
}

Next Steps

API Reference

Explore the complete TypeScript API documentation

Express Integration

Learn more about Express integration patterns

Error Handling

Advanced error handling patterns

Custom Responses

Create type-safe custom response formats

Build docs developers (and LLMs) love