Nexus NF

Error Handling

Learn how to handle errors gracefully in NexusNF applications

Error Handling

NexusNF provides automatic error handling for all endpoints. When an error occurs, it's automatically caught, formatted, and returned to the client in a consistent structure.

Error Response Format

All errors return a standardized ErrorResponse:

interface ErrorResponse {
  error: true;
  message: string;
  code?: string;
  details?: unknown;
}

Example error response:

{
  "error": true,
  "message": "Internal Server Error",
  "code": "500",
  "details": {
    "name": "Error",
    "message": "Database connection failed"
  }
}

In production mode (NODE_ENV !== 'dev'), error details like stack traces are automatically omitted for security.

Error Types

1. Validation Errors (400)

Automatically generated when Zod schema validation fails:

import { z } from 'zod';

const userSchema = z.object({
  name: z.string().min(1),
  email: z.string().email()
});

export class UserController extends ControllerBase {
  @Endpoint('create', { schema: userSchema })
  async createUser(data: z.infer<typeof userSchema>) {
    return { id: 123, ...data };
  }
}

Sending invalid data:

// Request
{ "name": "", "email": "not-an-email" }

// Response
{
  "error": true,
  "code": "400",
  "message": "Bad Request: Validation failed.",
  "details": [
    {
      "code": "too_small",
      "minimum": 1,
      "path": ["name"],
      "message": "String must contain at least 1 character(s)"
    },
    {
      "code": "invalid_string",
      "validation": "email",
      "path": ["email"],
      "message": "Invalid email"
    }
  ]
}

2. NATS Errors

NATS-specific errors are automatically formatted:

export class MessageController extends ControllerBase {
  @Endpoint('send')
  async sendMessage(data: any) {
    // If NATS connection fails or timeout occurs
    await this.natsConnection.request('other.service', data, { timeout: 1000 });
    return { sent: true };
  }
}

Response on NATS error:

{
  "error": true,
  "code": "TIMEOUT",
  "message": "Request timeout",
  "details": {
    "name": "NatsError",
    "stack": "..." // Only in development
  }
}

3. Application Errors (500)

Standard JavaScript errors thrown in your handlers:

export class UserController extends ControllerBase {
  @Endpoint('find')
  async findUser(data: { id: number }) {
    const user = await database.findById(data.id);

    if (!user) {
      throw new Error('User not found');
    }

    return user;
  }
}

Response:

{
  "error": true,
  "code": "500",
  "message": "Internal Server Error",
  "details": {
    "name": "Error",
    "message": "User not found",
    "stack": "..." // Only in development
  }
}

Error Handling Patterns

Try-Catch for Expected Errors

Handle expected errors gracefully:

export class PaymentController extends ControllerBase {
  @Endpoint('process')
  async processPayment(data: { amount: number; customerId: string }) {
    try {
      const payment = await paymentGateway.charge({
        amount: data.amount,
        customerId: data.customerId
      });

      return {
        transactionId: payment.id,
        status: 'success'
      };
    } catch (error) {
      if (error instanceof PaymentGatewayError) {
        // Handle specific payment errors
        throw new Error(`Payment failed: ${error.message}`);
      }

      // Re-throw unexpected errors
      throw error;
    }
  }
}

Validation Beyond Schema

Perform additional validation in your handler:

export class OrderController extends ControllerBase {
  @Endpoint('create', { schema: orderSchema })
  async createOrder(data: z.infer<typeof orderSchema>) {
    // Schema validation passed, now check business rules
    const user = await this.getUser(data.userId);
    if (!user.verified) {
      throw new NatsError('User account not verified', '403');
    }

    const inventory = await this.checkInventory(data.productId);
    if (inventory < data.quantity) {
      throw new NatsError('Insufficient inventory', '400');
    }

    return await this.database.createOrder(data);
  }
}

Resource Cleanup

Ensure resources are cleaned up even when errors occur:

export class FileController extends ControllerBase {
  @Endpoint('process', { asBytes: true })
  async processFile(data: Uint8Array) {
    const tempFile = await this.saveTempFile(data);

    try {
      const result = await this.analyzeFile(tempFile);
      return result;
    } finally {
      // Always clean up, even if an error occurred
      await this.deleteTempFile(tempFile);
    }
  }
}

Development vs Production

Development Mode

In development (NODE_ENV=dev), error responses include full details:

{
  "error": true,
  "code": "500",
  "message": "Internal Server Error",
  "details": {
    "name": "Error",
    "message": "Database connection failed",
    "stack": "Error: Database connection failed\n    at UserController.findUser (/app/controllers/user.ts:15:13)\n    ..."
  }
}

Production Mode

In production, sensitive details are omitted:

{
  "error": true,
  "code": "500",
  "message": "Internal Server Error"
}

Never rely on error details being present in production. Always handle errors gracefully on the client side.

Logging Errors

NexusNF automatically logs errors using Winston:

// Automatic logging happens internally
// You'll see colored, formatted logs in your console

// [10:30:45.123] [error]: An error occurred during the processing of the message
// {
//   message: '{"error":true,"code":"500","message":"Internal Server Error"}',
//   responseTopic: '_INBOX.abc123'
// }

Custom Logging

Add your own logging for specific scenarios:

export class UserController extends ControllerBase {
  private logger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),
    transports: [
      new winston.transports.File({ filename: 'error.log', level: 'error' }),
      new winston.transports.File({ filename: 'combined.log' })
    ]
  });

  @Endpoint('create', { schema: userSchema })
  async createUser(data: z.infer<typeof userSchema>) {
    try {
      const user = await database.createUser(data);
      this.logger.info('User created', { userId: user.id, email: data.email });
      return user;
    } catch (error) {
      this.logger.error('Failed to create user', {
        error: error.message,
        data: data
      });
      throw error; // Re-throw for NexusNF error handling
    }
  }
}

Next Steps

  • Review Validation for preventing errors with schemas
  • Learn about Controllers for organizing your endpoints