Skip to main content

Overview

The T1 Component Library API uses a consistent error handling strategy across all endpoints. Errors are caught by a global error middleware that formats responses uniformly and logs errors for debugging.

Standard Error Response Format

All errors follow this JSON structure:
{
  "success": false,
  "message": "Descripción del error"
}
In development mode (NODE_ENV=development), the response includes a stack trace:
{
  "success": false,
  "message": "Descripción del error",
  "stack": "Error: ..\n    at ...\n    at ..."
}
Stack traces are only included in development to prevent exposing internal details in production.

Error Middleware Implementation

The error middleware is defined in server/src/middlewares/error.middleware.ts:
error.middleware.ts:10-44
export const errorMiddleware = (
  err: CustomError,
  req: Request,
  res: Response,
  _next: NextFunction
): void => {
  logger.error(`Error: ${err.message}`, { stack: err.stack });

  let statusCode = err.statusCode || 500;
  let message = err.message || 'Error interno del servidor';

  // Error de duplicado de MongoDB
  if (err.code === 11000) {
    statusCode = 400;
    const field = Object.keys(err.keyValue || {})[0];
    message = `El ${field} ya está registrado`;
  }

  // Error de validación de Mongoose
  if (err.name === 'ValidationError') {
    statusCode = 400;
  }

  // Error de Cast de MongoDB (ID inválido)
  if (err.name === 'CastError') {
    statusCode = 400;
    message = 'ID inválido';
  }

  res.status(statusCode).json({
    success: false,
    message,
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  });
};

Error Type Detection

The middleware handles specific error types:
  1. MongoDB Duplicate Key (code: 11000): Returns 400 with field name
  2. Mongoose Validation Error: Returns 400 for schema validation failures
  3. MongoDB Cast Error: Returns 400 for invalid ObjectId formats
  4. Custom Errors: Uses statusCode property if set
  5. Unknown Errors: Defaults to 500

HTTP Status Codes

The API uses standard HTTP status codes:
CodeDescriptionWhen Used
200OKSuccessful GET requests
201CreatedSuccessful POST requests (resource created)
400Bad RequestValidation errors, invalid data, duplicate keys
401UnauthorizedMissing/invalid JWT token, incorrect credentials
404Not FoundResource doesn’t exist
500Internal Server ErrorUnexpected server errors, database errors
503Service UnavailableDatabase disconnected or service degraded

Common Error Scenarios

Authentication Errors

Missing Token

Request:
GET /api/components/export
Response (401):
{
  "success": false,
  "message": "Token no proporcionado"
}

Invalid or Expired Token

Request:
GET /api/components/export
Authorization: Bearer invalid_token
Response (401):
{
  "success": false,
  "message": "Token inválido"
}

Invalid Credentials

Request:
POST /api/auth/login
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "wrong_password"
}
Response (401):
{
  "success": false,
  "message": "Credenciales inválidas"
}

Validation Errors

Missing Required Fields

Request:
POST /api/components/track
Content-Type: application/json

{
  "nombre": "Button"
  // Missing "accion" field
}
Response (400):
{
  "success": false,
  "message": "Datos inválidos"
}

Duplicate Email Registration

Request:
POST /api/auth/register
Content-Type: application/json

{
  "nombre": "Juan Pérez",
  "email": "[email protected]",
  "password": "password123"
}
Response (400):
{
  "success": false,
  "message": "El email ya está registrado"
}

Invalid MongoDB ObjectId

Request:
GET /api/users/invalid_id
Response (400):
{
  "success": false,
  "message": "ID inválido"
}

Server Errors

Database Connection Error

Request:
GET /api/components/stats
Response (500):
{
  "success": false,
  "message": "Error interno del servidor"
}

Service Degraded (Health Check)

Request:
GET /api/health
Response (503):
{
  "success": true,
  "status": "degraded",
  "timestamp": "2025-11-27T12:00:00.000Z",
  "uptime": "120s",
  "services": {
    "database": {
      "status": "disconnected",
      "connected": false
    }
  },
  "system": {
    "nodeVersion": "v18.19.0",
    "memory": {
      "heapUsed": "45MB",
      "heapTotal": "65MB",
      "rss": "85MB"
    }
  }
}

Error Handling on the Client

Basic Try-Catch

import { api } from '@/lib/api';

try {
  const response = await api.trackInteraction({
    nombre: 'Button',
    accion: 'click',
    tipo_usuario: 'anonymous'
  });
  console.log('Success:', response.data);
} catch (error) {
  if (error instanceof Error) {
    console.error('Error:', error.message);
  }
}

Checking Response Status

const response = await fetch(`${API_URL}/components/track`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});

if (!response.ok) {
  const error = await response.json();
  
  switch (response.status) {
    case 400:
      console.error('Validation error:', error.message);
      break;
    case 401:
      console.error('Unauthorized:', error.message);
      // Redirect to login
      window.location.href = '/login';
      break;
    case 500:
      console.error('Server error:', error.message);
      break;
    default:
      console.error('Unknown error:', error.message);
  }
  
  throw new Error(error.message);
}

React Query Error Handling

import { useQuery } from '@tanstack/react-query';
import { api } from '@/lib/api';

function StatsComponent() {
  const { data, error, isError, isLoading } = useQuery({
    queryKey: ['stats'],
    queryFn: api.getStats,
    retry: (failureCount, error) => {
      // Don't retry on 4xx errors
      if (error instanceof Error && error.message.includes('401')) {
        return false;
      }
      return failureCount < 3;
    }
  });

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (isError) {
    return (
      <div className="error">
        <h3>Error fetching stats</h3>
        <p>{error.message}</p>
      </div>
    );
  }

  return <div>{/* Render stats */}</div>;
}

Global Error Handler

Create a custom error handler for all API calls:
lib/errorHandler.ts
export class ApiError extends Error {
  constructor(
    public statusCode: number,
    message: string,
    public details?: unknown
  ) {
    super(message);
    this.name = 'ApiError';
  }
}

export async function handleApiResponse<T>(response: Response): Promise<T> {
  if (!response.ok) {
    const error = await response.json().catch(() => ({
      message: 'Unknown error occurred'
    }));
    
    throw new ApiError(
      response.status,
      error.message || 'Request failed',
      error
    );
  }
  
  return response.json();
}
Use it in your API calls:
export const api = {
  trackInteraction: async (payload: TrackingPayload) => {
    const response = await fetch(`${API_URL}/components/track`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    
    return handleApiResponse<TrackingResponse>(response);
  }
};

Best Practices

1. Always Handle Errors

// Bad - Error will crash the app
const data = await api.getStats();

// Good - Error is caught and handled
try {
  const data = await api.getStats();
} catch (error) {
  showErrorToast(error.message);
}

2. Provide User-Friendly Messages

const ERROR_MESSAGES: Record<number, string> = {
  400: 'Please check your input and try again',
  401: 'Please log in to continue',
  404: 'The requested resource was not found',
  500: 'Something went wrong. Please try again later',
  503: 'Service is temporarily unavailable'
};

try {
  await api.trackInteraction(payload);
} catch (error) {
  const statusCode = error.statusCode || 500;
  const userMessage = ERROR_MESSAGES[statusCode] || 'An error occurred';
  showToast(userMessage);
}

3. Log Errors for Debugging

try {
  await api.trackInteraction(payload);
} catch (error) {
  // Log for debugging
  console.error('API Error:', {
    endpoint: '/components/track',
    payload,
    error: error.message,
    statusCode: error.statusCode
  });
  
  // Show user-friendly message
  showErrorToast('Failed to track interaction');
}

4. Handle Authentication Errors Globally

// Set up a response interceptor
async function apiCall(url: string, options: RequestInit) {
  const response = await fetch(url, options);
  
  if (response.status === 401) {
    // Clear auth state
    localStorage.removeItem('token');
    
    // Redirect to login
    window.location.href = '/login';
    
    throw new Error('Session expired');
  }
  
  return response;
}

5. Use React Query for Automatic Retries

const { data } = useQuery({
  queryKey: ['stats'],
  queryFn: api.getStats,
  retry: (failureCount, error) => {
    // Don't retry on client errors (4xx)
    if (error.statusCode >= 400 && error.statusCode < 500) {
      return false;
    }
    // Retry server errors (5xx) up to 3 times
    return failureCount < 3;
  },
  retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000)
});

6. Validate Data Before Sending

function trackInteraction(nombre: string, accion: string) {
  // Client-side validation
  if (!nombre || !accion) {
    showErrorToast('Component name and action are required');
    return;
  }
  
  if (nombre.length > 50) {
    showErrorToast('Component name is too long');
    return;
  }
  
  // Make API call
  return api.trackInteraction({ nombre, accion, tipo_usuario: 'anonymous' });
}

Client Setup

Learn how to configure the API client and make requests

Authentication

Understand JWT authentication and protected endpoints

Build docs developers (and LLMs) love