Skip to main content

Overview

StatusFlow provides flexible options for customizing error responses. This guide shows you how to override default messages, add custom fields, and create tailored response formats for your API.

Customization Options

StatusFlow offers several ways to customize responses:

Override Messages

Replace default messages with custom text

Extra Fields

Add custom data to response objects

Exception Details

Include context with exception details

Success Responses

Create uniform success response formats

Overriding Default Messages

Customize the message in your error responses:

Using StatusFlow Function

The overrideMessage parameter replaces the default message:
import { StatusFlow } from 'status-flow';

app.get('/users/:id', (req, res) => {
  const user = database.findUser(req.params.id);
  
  if (!user) {
    const response = StatusFlow({
      code: 404,
      lang: 'en',
      overrideMessage: 'The requested user does not exist in our system',
    });
    
    return res.status(404).json(response);
  }
  
  res.json({ success: true, data: user });
});
Response:
{
  "code": 404,
  "success": false,
  "message": "The requested user does not exist in our system",
  "info": {
    "name": "Not Found",
    "category": "Client Error",
    "description": "The requested resource was not found",
    "possibleCauses": [
      "Incorrect URL",
      "Resource deleted",
      "Resource never existed"
    ]
  }
}

Using HTTP Exceptions

Pass a custom message to the exception constructor:
import { NotFoundException, BadRequestException } from 'status-flow';

app.post('/products/:id/purchase', (req, res) => {
  const product = database.findProduct(req.params.id);
  
  if (!product) {
    throw new NotFoundException(
      'This product is no longer available for purchase'
    );
  }
  
  if (product.stock === 0) {
    throw new BadRequestException(
      'Product is currently out of stock. Please check back later.'
    );
  }
  
  // Process purchase...
});
Custom messages are especially useful for providing user-friendly error descriptions that make sense in your application’s context.

Adding Extra Fields

Include additional data in your responses using the extra parameter:

Basic Usage

import { StatusFlow } from 'status-flow';

app.post('/orders', (req, res) => {
  const { productId, quantity } = req.body;
  const product = database.findProduct(productId);
  
  if (!product) {
    const response = StatusFlow({
      code: 404,
      lang: 'en',
      overrideMessage: 'Product not found',
      extra: {
        requestedProductId: productId,
        availableProducts: database.getAvailableProductIds(),
        timestamp: new Date().toISOString(),
      },
    });
    
    return res.status(404).json(response);
  }
  
  if (quantity > product.stock) {
    const response = StatusFlow({
      code: 400,
      lang: 'en',
      overrideMessage: 'Insufficient stock',
      extra: {
        requestedQuantity: quantity,
        availableStock: product.stock,
        productName: product.name,
        estimatedRestockDate: product.restockDate,
      },
    });
    
    return res.status(400).json(response);
  }
  
  // Process order...
});
Response:
{
  "code": 400,
  "success": false,
  "message": "Insufficient stock",
  "requestedQuantity": 10,
  "availableStock": 3,
  "productName": "Wireless Mouse",
  "estimatedRestockDate": "2026-03-15",
  "info": {
    "name": "Bad Request",
    "category": "Client Error",
    "description": "The request could not be understood or was missing required parameters",
    "possibleCauses": [...]
  }
}
All fields from the extra object are merged into the top-level response. This makes it easy for clients to access custom data without nesting.

Advanced Example: Validation Errors

Provide detailed validation feedback:
import { StatusFlow } from 'status-flow';

app.post('/users', (req, res) => {
  const { email, password, age } = req.body;
  const errors = [];
  
  if (!email || !email.includes('@')) {
    errors.push({
      field: 'email',
      message: 'Valid email address is required',
      value: email || null,
    });
  }
  
  if (!password || password.length < 8) {
    errors.push({
      field: 'password',
      message: 'Password must be at least 8 characters',
      value: password ? '***' : null,
    });
  }
  
  if (age && (age < 18 || age > 120)) {
    errors.push({
      field: 'age',
      message: 'Age must be between 18 and 120',
      value: age,
    });
  }
  
  if (errors.length > 0) {
    const response = StatusFlow({
      code: 400,
      lang: 'en',
      overrideMessage: 'Validation failed',
      extra: {
        validationErrors: errors,
        errorCount: errors.length,
        receivedFields: Object.keys(req.body),
      },
    });
    
    return res.status(400).json(response);
  }
  
  // Create user...
});

Using Exception Details

When using HTTP exceptions with httpErrorMiddleware, use the details parameter:
import { BadRequestException, NotFoundException, ConflictException } from 'status-flow';

app.post('/users', async (req, res) => {
  const { email, username, password } = req.body;
  
  // Validation errors with details
  if (!email || !username || !password) {
    throw new BadRequestException(
      'Missing required fields',
      {
        required: ['email', 'username', 'password'],
        received: Object.keys(req.body),
        missing: [
          !email && 'email',
          !username && 'username',
          !password && 'password',
        ].filter(Boolean),
      }
    );
  }
  
  // Check for conflicts
  const existingEmail = await database.findByEmail(email);
  const existingUsername = await database.findByUsername(username);
  
  if (existingEmail || existingUsername) {
    throw new ConflictException(
      'User already exists',
      {
        conflicts: {
          email: !!existingEmail,
          username: !!existingUsername,
        },
        suggestions: {
          email: existingEmail ? generateAlternativeEmail(email) : null,
          username: existingUsername ? generateAlternativeUsername(username) : null,
        },
      }
    );
  }
  
  // Create user...
});
Response:
{
  "status": 409,
  "message": "User already exists",
  "details": {
    "conflicts": {
      "email": true,
      "username": false
    },
    "suggestions": {
      "email": "[email protected]",
      "username": null
    }
  }
}

Creating Success Responses

StatusFlow provides utilities for uniform success responses:

Using createSuccessResponse

import { createSuccessResponse } from 'status-flow';

app.get('/users/:id', async (req, res) => {
  const user = await database.findUser(req.params.id);
  
  if (!user) {
    throw new NotFoundException('User not found');
  }
  
  const response = createSuccessResponse(
    user,
    'User retrieved successfully',
    200
  );
  
  res.json(response);
});
Response:
{
  "status": 200,
  "message": "User retrieved successfully",
  "data": {
    "id": "123",
    "name": "John Doe",
    "email": "[email protected]"
  }
}

Custom Success Response Wrapper

Create your own success response format:
function apiResponse<T>(data: T, message = 'Success', metadata = {}) {
  return {
    success: true,
    timestamp: new Date().toISOString(),
    message,
    data,
    ...metadata,
  };
}

app.get('/users', async (req, res) => {
  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);
  
  const response = apiResponse(
    users,
    'Users retrieved successfully',
    {
      pagination: {
        page,
        limit,
        total,
        pages: Math.ceil(total / limit),
      },
    }
  );
  
  res.json(response);
});

Practical Examples

Rate Limiting Response

import { StatusFlow } from 'status-flow';

const rateLimiter = (req: Request, res: Response, next: NextFunction) => {
  const limit = getRateLimit(req.ip);
  
  if (limit.exceeded) {
    const response = StatusFlow({
      code: 429,
      lang: 'en',
      overrideMessage: 'Rate limit exceeded',
      extra: {
        limit: limit.max,
        remaining: 0,
        resetAt: limit.resetTime,
        retryAfter: limit.retryAfter,
      },
    });
    
    return res.status(429).json(response);
  }
  
  next();
};

Authentication Response

import { UnauthorizedException } from 'status-flow';

app.post('/auth/login', async (req, res) => {
  const { email, password } = req.body;
  
  const user = await database.findByEmail(email);
  const validPassword = user && await bcrypt.compare(password, user.passwordHash);
  
  if (!user || !validPassword) {
    throw new UnauthorizedException(
      'Invalid email or password',
      {
        attemptedEmail: email,
        timestamp: new Date().toISOString(),
        remainingAttempts: getRemainingLoginAttempts(email),
        lockoutTime: isAccountLocked(email) ? getLockoutTime(email) : null,
      }
    );
  }
  
  const token = generateToken(user);
  res.json({
    success: true,
    message: 'Login successful',
    data: { token, user: sanitizeUser(user) },
  });
});

Resource Creation Response

import { createSuccessResponse, ConflictException } from 'status-flow';

app.post('/projects', async (req, res) => {
  const { name, description } = req.body;
  
  const existing = await database.findProjectByName(name);
  if (existing) {
    throw new ConflictException(
      'Project name already exists',
      {
        existingProjectId: existing.id,
        suggestedNames: generateSimilarNames(name),
      }
    );
  }
  
  const project = await database.createProject({ name, description });
  
  const response = createSuccessResponse(
    project,
    'Project created successfully',
    201
  );
  
  res.status(201)
    .location(`/projects/${project.id}`)
    .json(response);
});

Maintenance Mode Response

import { StatusFlow } from 'status-flow';

const maintenanceMiddleware = (req: Request, res: Response, next: NextFunction) => {
  if (isMaintenanceMode()) {
    const maintenanceInfo = getMaintenanceInfo();
    
    const response = StatusFlow({
      code: 503,
      lang: 'en',
      overrideMessage: 'Service temporarily unavailable for maintenance',
      extra: {
        scheduledEnd: maintenanceInfo.endTime,
        estimatedDuration: maintenanceInfo.duration,
        reason: maintenanceInfo.reason,
        statusPage: 'https://status.example.com',
      },
    });
    
    return res.status(503)
      .set('Retry-After', maintenanceInfo.retryAfter)
      .json(response);
  }
  
  next();
};

Best Practices

Use the same response structure across your API:
// ✅ Good - consistent format
{ success: true, data: {...}, message: '...' }
{ success: false, error: {...}, message: '...' }

// ❌ Bad - inconsistent
{ result: {...} }  // Sometimes this
{ data: {...} }    // Sometimes that
Add context that helps developers debug issues:
extra: {
  timestamp: new Date().toISOString(),
  requestId: req.id,
  endpoint: req.path,
  method: req.method,
}
Be careful not to leak internal details:
// ✅ Good
throw new UnauthorizedException('Invalid credentials');

// ❌ Bad - reveals which field is wrong
throw new UnauthorizedException('Password is incorrect');
Respect the user’s language preference:
const lang = req.headers['x-lang'] === 'en' ? 'en' : 'es';

const messages = {
  en: 'User not found',
  es: 'Usuario no encontrado',
};

StatusFlow({
  code: 404,
  lang,
  overrideMessage: messages[lang],
});
Help users understand what to do next:
throw new BadRequestException(
  'Email format is invalid. Please provide a valid email address like [email protected]',
  { providedEmail: email }
);

Next Steps

TypeScript Usage

Type-safe custom responses with TypeScript

StatusFlow API

Complete API reference for StatusFlow function

Response Utilities

Learn about response helper functions

Error Handling

Error handling patterns and best practices

Build docs developers (and LLMs) love