Skip to main content

Overview

The MaqAgr API provides a rich set of utility functions organized into specialized modules. These utilities ensure code consistency, reduce boilerplate, and improve maintainability across the codebase.

Logger Utility

Centralized logging system using Winston with multiple transports, daily rotation, and structured JSON logging.

Configuration

Location: src/config/logger.js (re-exported from src/utils/logger.js)
import logger from './utils/logger.js';

Features

  • Multiple log levels: error, warn, info, http, debug
  • Daily log rotation: Keeps logs for 14 days, compresses old files
  • Environment-aware: Different output formats for development/production
  • Request correlation: Automatic request-id tracking
  • Slow query detection: Warns about database queries exceeding threshold

Usage

import logger from './utils/logger.js';

// Different log levels
logger.error('Database connection failed', { error: err });
logger.warn('High memory usage detected', { usage: '85%' });
logger.info('User registered successfully', { userId: 123 });
logger.http('GET /api/tractors - 200 OK');
logger.debug('Cache hit for key: user:123');

Log Files

Logs are stored in the logs/ directory:
logs/
├── combined.log           # All log levels
├── error.log              # Errors only
├── app-2026-03-11.log     # Daily rotation
└── app-2026-03-10.log.gz  # Compressed archives
In test environment (NODE_ENV=test), logging to console and files is disabled to keep test output clean.

Response Utility

Standardized response functions following the JSend specification. Location: src/utils/response.util.js

Available Functions

import { successResponse, createdResponse, noContentResponse } from './utils/response.util.js';

// 200 OK - General success
return successResponse(res, data, 'Tractores obtenidos exitosamente');

// 201 Created - Resource created
return createdResponse(res, newTractor, 'Tractor creado exitosamente');

// 204 No Content - Success with no response body
return noContentResponse(res);

Response Format

All responses follow the JSend standard: Success Response:
{
  "success": true,
  "message": "Operación exitosa",
  "data": { ... }
}
Error Response:
{
  "success": false,
  "message": "Descripción del error",
  "errors": [ ... ] // Optional validation errors
}

Validators Utility

Comprehensive validation functions for data sanitization and verification. Location: src/utils/validators.util.js

String Validators

import { isValidEmail, isValidPassword, getPasswordValidationErrors } from './utils/validators.util.js';

// Email validation
if (!isValidEmail(email)) {
  return validationErrorResponse(res, 'Email inválido');
}

// Password validation (min 8 chars, 1 uppercase, 1 number)
if (!isValidPassword(password)) {
  const errors = getPasswordValidationErrors(password);
  return validationErrorResponse(res, errors);
}

Number Validators

import {
  isPositiveNumber,
  isNonNegativeNumber,
  isPositiveInteger,
  isInRange
} from './utils/validators.util.js';

if (!isPositiveNumber(power)) {
  return validationErrorResponse(res, 'La potencia debe ser positiva');
}

if (!isNonNegativeNumber(weight)) {
  return validationErrorResponse(res, 'El peso no puede ser negativo');
}

if (!isPositiveInteger(tractorId)) {
  return validationErrorResponse(res, 'ID inválido');
}

if (!isInRange(slope, 0, 90)) {
  return validationErrorResponse(res, 'La pendiente debe estar entre 0 y 90 grados');
}

Enum & Special Validators

import {
  isValidEnum,
  isValidURL,
  isValidUUID,
  isValidDate,
  isValidCoordinates,
  isValidPhone
} from './utils/validators.util.js';

// Enum validation
const soilTypes = ['Franco', 'Arcilloso', 'Arenoso'];
if (!isValidEnum(soilType, soilTypes)) {
  return validationErrorResponse(res, 'Tipo de suelo inválido');
}

// URL validation
if (!isValidURL(websiteUrl)) {
  return validationErrorResponse(res, 'URL inválida');
}

// UUID validation (v4)
if (!isValidUUID(requestId)) {
  return validationErrorResponse(res, 'UUID inválido');
}

// Date validation
if (!isValidDate(registrationDate)) {
  return validationErrorResponse(res, 'Fecha inválida');
}

// Geographic coordinates
if (!isValidCoordinates(latitude, longitude)) {
  return validationErrorResponse(res, 'Coordenadas inválidas');
}

// Phone number (international/local)
if (!isValidPhone(phoneNumber)) {
  return validationErrorResponse(res, 'Teléfono inválido');
}

Object & Field Validation

import { hasRequiredProperties, validateFields } from './utils/validators.util.js';

// Check required properties
const { isValid, missingProps } = hasRequiredProperties(
  req.body,
  ['name', 'email', 'password']
);

if (!isValid) {
  return validationErrorResponse(res, `Campos faltantes: ${missingProps.join(', ')}`);
}

// Validate multiple fields at once
const { isValid: allValid, errors } = validateFields(
  req.body,
  {
    email: {
      required: true,
      validator: isValidEmail,
      message: 'Email inválido'
    },
    age: {
      required: false,
      validator: (v) => isPositiveNumber(v) && isInRange(v, 0, 120),
      message: 'Edad debe estar entre 0 y 120'
    }
  }
);

if (!allValid) {
  return validationErrorResponse(res, errors);
}

SQL Injection Protection

While PostgreSQL parameterized queries are the primary defense, this utility provides defense-in-depth.
import { sanitizeSQLInput } from './utils/validators.util.js';

// Remove SQL injection patterns
const safeName = sanitizeSQLInput(userInput);
// Removes: ', --, /*, */, ;

// Still use parameterized queries!
await pool.query(
  'SELECT * FROM users WHERE name = $1',
  [safeName]
);

AsyncHandler Utility

Wrapper for async route handlers that eliminates repetitive try-catch blocks. Location: src/utils/asyncHandler.util.js

Basic Usage

import { asyncHandler } from './utils/asyncHandler.util.js';

// Without asyncHandler (verbose)
export const getTractor = async (req, res, next) => {
  try {
    const tractor = await Tractor.findById(req.params.id);
    return successResponse(res, tractor);
  } catch (error) {
    next(error);
  }
};

// With asyncHandler (clean)
export const getTractor = asyncHandler(async (req, res) => {
  const tractor = await Tractor.findById(req.params.id);
  return successResponse(res, tractor);
});

Strict Mode

For functions that may throw synchronous errors:
import { asyncHandlerStrict } from './utils/asyncHandler.util.js';

export const processTractor = asyncHandlerStrict(async (req, res) => {
  // Synchronous validation that might throw
  validateInput(req.body);
  
  // Async operations
  const result = await processData(req.body);
  return successResponse(res, result);
});

Error Classes

Custom error classes for consistent error handling. Location: src/utils/errors.util.js
import {
  AppError,
  ValidationError,
  AuthenticationError,
  AuthorizationError,
  NotFoundError
} from './utils/errors.util.js';

// Generic application error
throw new AppError('Something went wrong', 500);

// Validation error with details
throw new ValidationError(['Email is invalid', 'Password too short']);

// Authentication error (401)
throw new AuthenticationError('Invalid token');

// Authorization error (403)
throw new AuthorizationError('Admin access required');

// Not found error (404)
throw new NotFoundError('Tractor not found');

Cache Utility

Redis caching helpers for improved performance. Location: src/utils/cache.util.js
import { cacheGet, cacheSet, cacheDel } from './utils/cache.util.js';

// Get from cache
const cachedTractors = await cacheGet('tractors:all');
if (cachedTractors) {
  return successResponse(res, JSON.parse(cachedTractors));
}

// Fetch from DB and cache
const tractors = await Tractor.findAll();
await cacheSet('tractors:all', JSON.stringify(tractors), 3600); // TTL: 1 hour

// Invalidate cache
await cacheDel('tractors:all');

JWT Utility

JSON Web Token generation and verification. Location: src/utils/jwt.util.js
import { generateToken, verifyToken } from './utils/jwt.util.js';

// Generate token
const token = generateToken({
  user_id: user.id,
  email: user.email,
  role_id: user.role_id
});

// Verify token
try {
  const decoded = verifyToken(token);
  console.log('User ID:', decoded.user_id);
} catch (error) {
  // Token invalid or expired
  throw new AuthenticationError('Invalid token');
}

Best Practices

Use validators for all user input before processing:
const { isValid, errors } = validateFields(req.body, rules);
if (!isValid) return validationErrorResponse(res, errors);
Wrap all async route handlers to automatically catch errors:
export const myRoute = asyncHandler(async (req, res) => { ... });
Always use response utilities for consistency:
return successResponse(res, data, message);
return notFoundResponse(res, 'Resource not found');
Include relevant metadata in logs:
logger.info('User registered', { userId, email, ipAddress });
logger.error('Payment failed', { userId, amount, error: err.message });

Error Handling

Learn about error handling patterns

Testing

Test utilities and helpers

Build docs developers (and LLMs) love