Skip to main content

Overview

StatusFlow provides flexible error handling for Express applications through HTTP exceptions and the StatusFlow function. This guide covers different patterns, async error handling, and best practices.

Error Handling Approaches

StatusFlow offers two main approaches for error handling:

HTTP Exceptions

Throw exception classes for clean, type-safe error handling

StatusFlow Function

Use the StatusFlow function for custom response generation

Using HTTP Exceptions

HTTP exceptions provide a clean, object-oriented approach to error handling:

Available Exception Classes

StatusFlow includes exception classes for common HTTP errors:
import {
  HttpException,              // Base class (status: number, message: string, details?: unknown)
  BadRequestException,        // 400
  UnauthorizedException,      // 401
  ForbiddenException,         // 403
  NotFoundException,          // 404
  ConflictException,          // 409
  InternalServerErrorException, // 500
} from 'status-flow';

Basic Usage

Throw exceptions directly in your route handlers:
import { NotFoundException, BadRequestException } from 'status-flow';

app.get('/users/:id', (req, res) => {
  const { id } = req.params;
  
  if (!id) {
    throw new BadRequestException('User ID is required');
  }
  
  const user = database.findUser(id);
  
  if (!user) {
    throw new NotFoundException('User not found');
  }
  
  res.json({ success: true, data: user });
});

Adding Context with Details

Include additional context through the details parameter:
app.post('/users', (req, res) => {
  const { email, password } = req.body;
  
  // Validation with detailed error information
  if (!email || !password) {
    throw new BadRequestException(
      'Missing required fields',
      {
        required: ['email', 'password'],
        received: { email: !!email, password: !!password },
      }
    );
  }
  
  // Check for existing user
  const existingUser = database.findByEmail(email);
  if (existingUser) {
    throw new ConflictException(
      'User already exists',
      { email, userId: existingUser.id }
    );
  }
  
  const user = database.createUser({ email, password });
  res.status(201).json({ success: true, data: user });
});
The details field is included in the error response, making it easy to provide debugging information and context to API consumers.

Custom HTTP Status Codes

For custom status codes, use the base HttpException class:
import { HttpException } from 'status-flow';

app.post('/upload', (req, res) => {
  const fileSize = req.headers['content-length'];
  
  if (fileSize > MAX_FILE_SIZE) {
    throw new HttpException(
      413,
      'Payload too large',
      { maxSize: MAX_FILE_SIZE, receivedSize: fileSize }
    );
  }
  
  // Process upload...
});

Using the StatusFlow Function

The StatusFlow function generates comprehensive bilingual responses:

Basic Usage

import { StatusFlow } from 'status-flow';

app.get('/status/:code', (req, res) => {
  const code = parseInt(req.params.code);
  const lang = req.headers['x-lang'] === 'en' ? 'en' : 'es';
  
  const response = StatusFlow({ code, lang });
  res.status(code).json(response);
});

With Custom Messages and Extra Data

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  
  const user = authenticate(username, password);
  
  if (!user) {
    const response = StatusFlow({
      code: 401,
      lang: 'en',
      overrideMessage: 'Invalid credentials',
      extra: {
        attemptedUsername: username,
        timestamp: new Date().toISOString(),
      },
    });
    return res.status(401).json(response);
  }
  
  res.json({ success: true, token: generateToken(user) });
});

StatusFlow Options

interface StatusFlowOptions {
  code: number;              // HTTP status code (required)
  lang?: 'es' | 'en';       // Language (default: 'es')
  extra?: Record<string, any>; // Additional fields to include
  overrideMessage?: string;  // Custom message (overrides default)
}
Use the extra parameter to include any additional data in the response. All fields from extra are merged into the top-level response object.

Async Error Handling

Express doesn’t automatically catch errors in async functions. Here are the recommended patterns:

Pattern 1: Try-Catch Blocks

Wrap async operations in try-catch blocks:
app.get('/users/:id', async (req, res) => {
  try {
    const { id } = req.params;
    
    if (!id) {
      throw new BadRequestException('User ID is required');
    }
    
    const user = await database.findUser(id);
    
    if (!user) {
      throw new NotFoundException('User not found');
    }
    
    res.json({ success: true, data: user });
  } catch (error) {
    // Pass to error middleware
    next(error);
  }
});

Pattern 2: Async Handler Wrapper

Create a utility function to wrap async handlers:
const asyncHandler = (fn: Function) => {
  return (req: Request, res: Response, next: NextFunction) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
};

// Usage
app.get('/users/:id', asyncHandler(async (req, res) => {
  const { id } = req.params;
  
  if (!id) {
    throw new BadRequestException('User ID is required');
  }
  
  const user = await database.findUser(id);
  
  if (!user) {
    throw new NotFoundException('User not found');
  }
  
  res.json({ success: true, data: user });
}));

Pattern 3: Express Async Errors

Use the express-async-errors package for automatic error handling:
npm install express-async-errors
import 'express-async-errors'; // Import at the top of your file
import express from 'express';
import { statusFlowMiddleware, NotFoundException } from 'status-flow';

const app = express();

// Now async errors are automatically caught
app.get('/users/:id', async (req, res) => {
  const user = await database.findUser(req.params.id);
  
  if (!user) {
    throw new NotFoundException('User not found');
  }
  
  res.json({ success: true, data: user });
});

app.use(statusFlowMiddleware);
When using express-async-errors, import it before creating your Express app. This ensures it patches Express properly.

Error Propagation Patterns

Service Layer Pattern

Throw exceptions in service layers and let them bubble up:
// services/userService.ts
import { NotFoundException, ConflictException } from 'status-flow';

export class UserService {
  async getUser(id: string) {
    const user = await database.findUser(id);
    
    if (!user) {
      throw new NotFoundException('User not found', { userId: id });
    }
    
    return user;
  }
  
  async createUser(email: string, password: string) {
    const existing = await database.findByEmail(email);
    
    if (existing) {
      throw new ConflictException('Email already registered', { email });
    }
    
    return await database.createUser({ email, password });
  }
}

// routes/users.ts
import { UserService } from '../services/userService';
import { BadRequestException } from 'status-flow';

const userService = new UserService();

app.get('/users/:id', async (req, res) => {
  const { id } = req.params;
  
  if (!id) {
    throw new BadRequestException('User ID is required');
  }
  
  // Exception from service will automatically propagate
  const user = await userService.getUser(id);
  res.json({ success: true, data: user });
});

Middleware Chain Pattern

Throw exceptions in middleware:
import { UnauthorizedException, ForbiddenException } from 'status-flow';

// Authentication middleware
const requireAuth = (req: Request, res: Response, next: NextFunction) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  
  if (!token) {
    throw new UnauthorizedException('Authentication required');
  }
  
  try {
    req.user = verifyToken(token);
    next();
  } catch (error) {
    throw new UnauthorizedException('Invalid token');
  }
};

// Authorization middleware
const requireAdmin = (req: Request, res: Response, next: NextFunction) => {
  if (!req.user || req.user.role !== 'admin') {
    throw new ForbiddenException('Admin access required');
  }
  next();
};

// Protected route
app.delete('/users/:id', requireAuth, requireAdmin, (req, res) => {
  // Only admins can reach this point
  const result = database.deleteUser(req.params.id);
  res.json({ success: true, data: result });
});

Choosing Between Middleware

Use statusFlowMiddleware when you need:
  • Bilingual response support
  • Detailed HTTP status metadata
  • Comprehensive error information with causes and descriptions
  • Custom error objects with a code property
import { statusFlowMiddleware } from 'status-flow';

app.use(statusFlowMiddleware);

// Throw errors with code property
app.get('/error', (req, res) => {
  throw { code: 404, message: 'Custom error', extra: { foo: 'bar' } };
});
Response format:
{
  "code": 404,
  "success": false,
  "message": "Custom error",
  "foo": "bar",
  "info": {
    "name": "Not Found",
    "category": "Client Error",
    "description": "...",
    "possibleCauses": [...]
  }
}

Best Practices

Use throw to trigger error handling, not return. This ensures errors are caught by middleware:
// ✅ Good
if (!user) {
  throw new NotFoundException('User not found');
}

// ❌ Bad
if (!user) {
  return res.status(404).json({ error: 'User not found' });
}
Include relevant details to help with debugging:
throw new NotFoundException('User not found', {
  userId: id,
  searchedIn: 'database',
  timestamp: Date.now(),
});
Choose the most appropriate exception class for the error:
// ✅ Good - specific exception
throw new ConflictException('Email already exists');

// ❌ Bad - generic exception
throw new HttpException(409, 'Email already exists');
Always use try-catch, async handlers, or express-async-errors for async routes:
// ✅ Good
app.get('/users', async (req, res, next) => {
  try {
    const users = await database.getUsers();
    res.json(users);
  } catch (error) {
    next(error);
  }
});
Throw validation errors before performing expensive operations:
app.post('/users', async (req, res) => {
  // Validate first
  if (!req.body.email) {
    throw new BadRequestException('Email is required');
  }
  
  // Then perform database operations
  const user = await database.createUser(req.body);
  res.json(user);
});

Next Steps

Custom Responses

Learn how to customize error messages and response formats

TypeScript Usage

Type-safe error handling with TypeScript

HTTP Exceptions API

Complete API reference for exception classes

Middleware API

Detailed middleware configuration options

Build docs developers (and LLMs) love