Skip to main content

Error Handling

The Tanqueo Backend API uses consistent error handling patterns across all endpoints with standardized error responses and HTTP status codes.

Error Response Format

All errors return a JSON object with an error field:
{
  "error": "Descriptive error message"
}

Example Error Response

curl http://localhost:5000/api/tanqueos \
  -H "Authorization: Bearer invalid-token"
Response (401):
{
  "error": "Token inválido"
}

HTTP Status Codes

The API uses standard HTTP status codes to indicate success or failure:

Success Codes

CodeMeaningUsage
200OKSuccessful GET, PUT, DELETE
201CreatedSuccessful POST (resource created)

Client Error Codes (4xx)

CodeMeaningCommon Scenarios
400Bad RequestMissing required fields, validation errors, malformed data
401UnauthorizedMissing or invalid authentication token
404Not FoundResource doesn’t exist, route not found

Server Error Codes (5xx)

CodeMeaningCommon Scenarios
500Internal Server ErrorUnexpected errors, database connection issues

Common Error Scenarios

Authentication Errors

Missing Token

Request:
curl http://localhost:5000/api/tanqueos
Response (401):
{
  "error": "Token no proporcionado"
}

Invalid Token

Request:
curl http://localhost:5000/api/tanqueos \
  -H "Authorization: Bearer expired-or-invalid-token"
Response (401):
{
  "error": "Token inválido"
}

User Not Found

Token is valid but user doesn’t exist in usuarios table: Response (401):
{
  "error": "Usuario no encontrado en la base de datos"
}

Login Errors

Missing Credentials

Request:
curl -X POST http://localhost:5000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]"}'
Response (400):
{
  "error": "Email y contraseña son requeridos"
}

Invalid Credentials

Request:
curl -X POST http://localhost:5000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "wrong-password"
  }'
Response (400):
{
  "error": "Invalid login credentials"
}
The exact error message comes from Supabase Auth and may vary. Common messages include “Invalid login credentials” or “Email not confirmed”.

Refresh Token Errors

Missing Refresh Token

Request:
curl -X POST http://localhost:5000/api/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{}'
Response (400):
{
  "error": "Refresh token es requerido"
}

Invalid or Expired Refresh Token

Response (401):
{
  "error": "Refresh token inválido o expirado"
}

Resource Not Found

Route Not Found

Request:
curl http://localhost:5000/api/nonexistent
Response (404):
{
  "error": "Ruta no encontrada"
}

Record Not Found

Request:
curl http://localhost:5000/api/tanqueos/99999 \
  -H "Authorization: Bearer valid-token"
Response (404):
{
  "error": "Tanqueo no encontrado"
}

Database Errors

Query Error

When a database query fails (constraint violation, type mismatch, etc.): Response (400):
{
  "error": "duplicate key value violates unique constraint \"unique_placa\""
}
Database error messages are passed through from Supabase/PostgreSQL. They provide detailed information for debugging.

Server Errors

Internal Server Error

Unexpected errors that weren’t handled explicitly: Response (500):
{
  "error": "Error en el servidor"
}

Error Handling Patterns

Controller Pattern

All controllers follow this consistent error handling pattern:
src/controllers/tanqueos.controller.ts
export const tanqueosController = {
  async getById(req: AuthRequest, res: Response): Promise<void> {
    try {
      const { id } = req.params;
      
      // Database query
      const { data, error } = await req.supabase
        ?.from('tanqueos')
        .select('*')
        .eq('id', id)
        .single() || await supabase
        .from('tanqueos')
        .select('*')
        .eq('id', id)
        .single();
      
      // Handle query errors (400)
      if (error) {
        console.error('Error en getById:', error);
        res.status(400).json({ error: error.message });
        return;
      }
      
      // Handle not found (404)
      if (!data) {
        res.status(404).json({ error: 'Tanqueo no encontrado' });
        return;
      }
      
      // Success (200)
      res.json(data);
    } catch (error) {
      // Unexpected errors (500)
      console.error('Error en getById:', error);
      res.status(500).json({ error: 'Error en el servidor' });
    }
  }
};

Three-Layer Error Handling

1

Database Error Handling

Check the error returned from Supabase queries:
const { data, error } = await supabase.from('table').select();

if (error) {
  res.status(400).json({ error: error.message });
  return;
}
2

Business Logic Validation

Validate data and business rules:
if (!data || data.length === 0) {
  res.status(404).json({ error: 'No se encontraron registros' });
  return;
}

if (!req.body.fecha || !req.body.conductor_id) {
  res.status(400).json({ error: 'Campos requeridos faltantes' });
  return;
}
3

Global Exception Handler

Catch unexpected errors with try-catch:
try {
  // All logic here
} catch (error) {
  console.error('Error:', error);
  res.status(500).json({ error: 'Error en el servidor' });
}

Authentication Middleware Errors

The auth middleware handles errors before reaching controllers:
src/middleware/auth.middleware.ts
export async function authMiddleware(
  req: AuthRequest,
  res: Response,
  next: NextFunction
): Promise<void> {
  try {
    // Extract token
    const token = req.headers.authorization?.replace('Bearer ', '');
    
    if (!token) {
      res.status(401).json({ error: 'Token no proporcionado' });
      return;
    }
    
    // Verify with Supabase
    const { data: { user }, error } = await supabase.auth.getUser(token);
    
    if (error || !user) {
      res.status(401).json({ error: 'Token inválido' });
      return;
    }
    
    // Fetch user data
    const { data: userData, error: userError } = await supabase
      .from('usuarios')
      .select('*')
      .eq('id', user.id)
      .single();
    
    if (userError || !userData) {
      res.status(401).json({ error: 'Usuario no encontrado en la base de datos' });
      return;
    }
    
    // Attach to request and continue
    req.user = userData;
    req.accessToken = token;
    req.supabase = createAuthClient(token);
    
    next();
  } catch (error) {
    console.error('Error en auth middleware:', error);
    res.status(401).json({ error: 'Error de autenticación' });
  }
}

Error Logging

All errors are logged to the console for debugging:
console.error('Error en getAll:', error);
Console Output:
Error en getAll: Error: relation "tanqueos" does not exist
    at Object.from (/app/node_modules/@supabase/postgrest-js/dist/index.js:...)
In production, consider using a structured logging service (like Winston, Pino, or cloud logging) instead of console.error for better error tracking and alerting.

Client-Side Error Handling

try {
  const response = await fetch('http://localhost:5000/api/tanqueos', {
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    }
  });
  
  if (!response.ok) {
    const errorData = await response.json();
    throw new Error(errorData.error || 'Unknown error');
  }
  
  const data = await response.json();
  // Handle success
} catch (error) {
  // Handle error
  if (error.message === 'Token inválido') {
    // Redirect to login or refresh token
  } else {
    // Show error to user
    console.error('API Error:', error);
  }
}

Handling Token Expiration

async function makeAuthenticatedRequest(url: string, options: RequestInit) {
  let response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${accessToken}`
    }
  });
  
  // Token expired - try refresh
  if (response.status === 401) {
    const refreshed = await refreshAccessToken();
    
    if (refreshed) {
      // Retry with new token
      response = await fetch(url, {
        ...options,
        headers: {
          ...options.headers,
          'Authorization': `Bearer ${newAccessToken}`
        }
      });
    } else {
      // Refresh failed - redirect to login
      window.location.href = '/login';
    }
  }
  
  return response;
}

Error Prevention Best Practices

Validate Input

Check required fields and data types before processing

Use TypeScript

Leverage type checking to catch errors at compile time

Handle Nulls

Check for null/undefined before accessing properties

Test Edge Cases

Test with invalid data, missing fields, and edge cases

Input Validation Example

async create(req: AuthRequest, res: Response): Promise<void> {
  try {
    const { fecha, conductor_id, placa_id, bomba_id, area_operacion_id } = req.body;
    
    // Validate required fields
    if (!fecha || !conductor_id || !placa_id || !bomba_id || !area_operacion_id) {
      res.status(400).json({ 
        error: 'Campos requeridos: fecha, conductor_id, placa_id, bomba_id, area_operacion_id' 
      });
      return;
    }
    
    // Validate data types
    if (typeof conductor_id !== 'number' || conductor_id <= 0) {
      res.status(400).json({ error: 'conductor_id debe ser un número positivo' });
      return;
    }
    
    // Validate date format
    if (!/^\d{4}-\d{2}-\d{2}$/.test(fecha)) {
      res.status(400).json({ error: 'fecha debe tener formato YYYY-MM-DD' });
      return;
    }
    
    // Proceed with creation
    const { data, error } = await req.supabase
      ?.from('tanqueos')
      .insert(req.body)
      .select()
      .single();
    
    // ... rest of handler
  } catch (error) {
    console.error('Error en create:', error);
    res.status(500).json({ error: 'Error en el servidor' });
  }
}

Summary

ScenarioStatus CodeError Message Pattern
Missing auth token401Token no proporcionado
Invalid auth token401Token inválido
Missing required fields400[Field] es requerido
Database query error400Database error message
Resource not found404[Resource] no encontrado
Route not found404Ruta no encontrada
Unexpected error500Error en el servidor
All error responses include detailed error messages to help developers debug issues. In production, consider sanitizing database error messages to avoid exposing sensitive schema information.

Build docs developers (and LLMs) love