Skip to main content

System overview

DAF Backend implements a dual-system architecture that operates two independent business domains:

POS System

Database: Separate PostgreSQL database with credential-based connectionsPurpose: In-store retail operations including inventory, invoices, clients, suppliersPrefix: /api/pos/*

E-commerce System

Database: Separate PostgreSQL database with pooled connectionsPurpose: Online shopping platform with cart, products, and paymentsPrefix: /api/ecom/*

Application entry points

The application has two main entry files:
1

Server initialization (src/server.js)

The server entry point loads environment variables and starts the Express server:
src/server.js
require('dotenv').config();

const app = require('./app');

// Obtener puerto del .env o defecto
const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`🚀 Servidor ejecutándose en http://localhost:${PORT}`);
});
This file:
  • Loads .env configuration
  • Imports the Express app
  • Starts the HTTP server on the specified port
2

Application configuration (src/app.js)

The app configuration defines middleware, routes, and error handlers:
src/app.js:27-38
const app = express();

let corsConfiguration = {
  origin: process.env.FRONTEND_IP ? [process.env.FRONTEND_IP] : ['http://localhost:5173', 'http://localhost:5174'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}
app.use(cors(corsConfiguration));

app.use(express.json());

MVC architecture pattern

DAF Backend follows the Model-View-Controller (MVC) pattern, adapted for a REST API:
src/
├── routes/          → Define API endpoints and HTTP methods
├── controllers/     → Handle business logic and request/response
├── models/          → Database queries and data access
├── dtos/            → Data validation and transformation
├── middlewares/     → Authentication and file upload processing
└── config/          → Database connections and configuration

Routes layer

Routes define the API endpoints and map them to controller functions:
src/app.js:42-48
// RUTAS POS
app.use('/api/pos/auth', require('./routes/pos.auth.routes'));
app.use('/api/pos/cliente', clienteRoutes);
app.use('/api/pos/unidadmedida', unidadMedidaRoutes);
app.use('/api/pos/transaccion', transaccionRoutes);
app.use('/api/pos/producto', require('./routes/pos.producto.routes'));
app.use('/api/pos/estandar', require('./routes/pos.estandar.routes'));
Example POS auth routes:
src/routes/pos.auth.routes.js
const express = require('express');
const router = express.Router();
const { login } = require('../controllers/pos.auth.controller.js');
const { verifyToken } = require('../middlewares/pos.auth.middleware.js');
const { getAccess } = require('../controllers/pos.access.controller.js');

// POS Routes
router.post('/login', login);
router.get('/access', verifyToken, getAccess);

module.exports = router;

Controllers layer

Controllers handle request processing, validation, and response formatting:
const create = async (req, res) => {
    // 1. Validar datos de entrada
    const errors = validateClienteDTO(req.body);
    if (errors.length) return res.status(400).json({ errors });

    const pool = connectFromJWT(req);

    try {
        const result = await modeloCliente.createCliente(pool, {
            ...req.body
        });

        res.status(201).json({
            message: "Cliente creado exitosamente",
            data: result
        });

    } catch (error) {
        console.error("Error en createCliente:", error.message);
        res.status(500).json({ message: "Error interno al crear cliente", detail: error.message });
    } finally {
        await pool.end();
    }
};
Key Difference: POS controllers create and close database pools per request, while e-commerce controllers use a shared connection pool.

Models layer

Models encapsulate database queries and data access logic:
src/models/cliente.model.js:43-55
const getAllClientes = async (pool, page = 1) => {
  const limit = 20;
  const offset = (page - 1) * limit;

  const query = `
        SELECT * FROM public.cliente WHERE cli_estado = '${process.env.ACTIVE_STATUS_INDEPENDENT}'
        ORDER BY cli_nombre ASC
        LIMIT $1 OFFSET $2
    `;

  const result = await pool.query(query, [limit, offset]);
  return result.rows;
};

DTOs (Data Transfer Objects)

DTOs validate and transform incoming request data:
src/dtos/cliente.dto.js:3-43
const validateClienteDTO = (data, isUpdate = false) => {
  const errors = [];
  const validStatuses = ['ACT', 'INA', 'SUS'];

  // Validación de Ciudad (FK)
  if (!isUpdate || data.ct_codigo !== undefined) {
    if (!data.ct_codigo || data.ct_codigo.length > 10) {
      errors.push('ct_codigo es requerido y máximo 10 caracteres');
    }
  }

  // Nombre
  if (!isUpdate || data.cli_nombre !== undefined) {
    if (!data.cli_nombre || data.cli_nombre.length > 120) {
      errors.push('cli_nombre es requerido y máximo 120 caracteres');
    }
  }

  // RUC / Cédula (13 dígitos)
  if (!isUpdate || data.cli_ruc_ced !== undefined) {
    if (!data.cli_ruc_ced || !/^\d/.test(data.cli_ruc_ced)) {
      errors.push('cli_ruc_ced es requerido y debe ser numérico');
    }
  }

  // Teléfono (10 dígitos)
  if (!isUpdate || data.cli_telefono !== undefined) {
    if (!data.cli_telefono || !/^\d{10}$/.test(data.cli_telefono)) {
      errors.push('cli_telefono es requerido y debe tener 10 dígitos numéricos');
    }
  }

  // Email
  if (!isUpdate || data.cli_mail !== undefined) {
    if (!data.cli_mail || data.cli_mail.length > 60) {
      errors.push('cli_mail es requerido y máximo 60 caracteres');
    }
  }

  return errors;
};

Middleware pipeline

Requests flow through several middleware layers:

Global middleware

1

CORS (Cross-Origin Resource Sharing)

Configured to allow requests from specified frontend origins:
src/app.js:29-35
let corsConfiguration = {
  origin: process.env.FRONTEND_IP ? [process.env.FRONTEND_IP] : ['http://localhost:5173', 'http://localhost:5174'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}
app.use(cors(corsConfiguration));
2

JSON body parser

Parses incoming JSON request bodies:
src/app.js:37
app.use(express.json());
3

Static file serving

Serves uploaded product images:
src/app.js:78
app.use('/images', express.static(path.join(__dirname, 'images')));

Route-specific middleware

JWT token verification middleware protects routes:
src/middlewares/pos.auth.middleware.js:4-33
const verifyToken = (req, res, next) => {
  const authHeader = req.headers['authorization'];

  if (!authHeader) {
    return res.status(401).json({
      message: 'Token no proporcionado',
    });
  }

  const token = authHeader.split(' ')[1]; // Authorization: Bearer {TOKEN}

  if (!token) {
    return res.status(401).json({
      message: 'Token inválido',
    });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);

    // 🔒 Se mantiene compatibilidad con todo lo existente
    req.user = decoded;
    next();
    
  } catch (error) {
    return res.status(401).json({
      message: 'Token inválido o expirado',
    });
  }
};

Database connection strategy

The two systems use different connection strategies:

POS connection pooling

Each POS request creates a new connection pool with user credentials from JWT:
src/config/db_pos.js
const { Pool } = require('pg');
require('dotenv').config();

function getConnectionWithCredentials(user, password) {
  return new Pool({
    host: process.env.POS_HOST,
    port: process.env.POS_PORT,
    database: process.env.POS_NAME,
    user,
    password,
  });
}

module.exports = {
  getConnectionWithCredentials,
};
Usage in controllers:
src/controllers/pos.cliente.controller.js:6-9
const connectFromJWT = (req) => {
    const { usuario, password } = req.user;
    return getConnectionWithCredentials(usuario, password);
};
Benefits:
  • Database-level security and authentication
  • Audit trails show actual user performing operations
  • Fine-grained permission control via PostgreSQL roles
Trade-offs:
  • Higher connection overhead (create and close pool per request)
  • Must manage pool cleanup in finally blocks

Error handling flow

The API implements a 404 catch-all handler:
src/app.js:80-82
app.use((req, res) => {
  res.status(404).json({ message: 'Not Found' });
});
Controller-level error handling follows a try-catch-finally pattern:
const getAll = async (req, res) => {
    const pool = connectFromJWT(req);
    try {
        const page = parseInt(req.query.page) || 1;
        const result = await modeloCliente.getAllClientes(pool, page);

        res.status(200).json({
            message: "Listado obtenido correctamente",
            count: result.length,
            data: result
        });
    } catch (error) {
        res.status(500).json({ message: error.message });
    } finally {
        await pool.end();  // Always close the pool
    }
};

Request lifecycle

Here’s a complete request lifecycle for a POS endpoint:
1

Client sends request

curl -X GET http://localhost:3000/api/pos/cliente \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
2

CORS middleware

Validates origin and adds CORS headers
3

Router matches route

Express router matches /api/pos/cliente to cliente routes
4

verifyToken middleware

Extracts and validates JWT token, decodes payload to req.user
5

Controller executes

  • Extracts credentials from req.user
  • Creates database pool with credentials
  • Calls model function to query database
6

Model queries database

Executes SQL query and returns results
7

Controller formats response

Formats data and sends JSON response with appropriate status code
8

Pool cleanup

finally block closes the database pool

Directory structure reference

daf-backend/
├── src/
│   ├── app.js                 → Express app configuration
│   ├── server.js              → Server entry point
│   ├── config/
│   │   ├── db_pos.js          → POS credential-based connections
│   │   ├── db_ecom.js         → E-commerce pooled connection
│   │   ├── db.factura.js      → Invoice-specific config
│   │   └── db.proveedor.js    → Supplier-specific config
│   ├── routes/
│   │   ├── pos.*.routes.js    → POS endpoint definitions
│   │   └── ecom.*.routes.js   → E-commerce endpoint definitions
│   ├── controllers/
│   │   ├── pos.*.controller.js  → POS business logic
│   │   └── ecom.*.controller.js → E-commerce business logic
│   ├── models/
│   │   └── *.model.js         → Database query functions
│   ├── dtos/
│   │   └── *.dto.js           → Validation schemas
│   ├── middlewares/
│   │   ├── pos.auth.middleware.js → JWT verification
│   │   ├── auth.middleware.js     → Simplified JWT verification
│   │   └── upload.js          → File upload handling
│   └── images/                → Uploaded product images
├── package.json
├── .env                       → Environment configuration
└── node_modules/

Next steps

Database

Deep dive into database configuration and schema

Authentication

Learn about JWT tokens and credential-based connections

Error Handling

Understand error responses and debugging

POS API Reference

Explore all POS endpoints

Build docs developers (and LLMs) love