Balsamoa Backend is a Node.js/Express REST API that follows a strict three-layer architecture: every feature is split into a routes file that maps HTTP verbs and paths, a controller file that validates input and orchestrates the response, and a model file that executes parameterized SQL queries against a PostgreSQL database hosted on Supabase. This separation keeps validation logic out of the database layer and keeps raw SQL out of the HTTP layer, making each piece independently testable and easy to extend.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/MateoNavarroMN/Balsamoa-Backend/llms.txt
Use this file to discover all available pages before exploring further.
Module structure
All business logic lives undersrc/modulos/. Each subdirectory represents one domain and contains exactly three files with a consistent naming convention.
server.mjs and mounted on the app with app.use(). Adding a new domain means creating a new folder and registering its router — no changes to existing modules required.
Request lifecycle
Every API call travels through the same three-layer pipeline before a JSON response is sent back to the client.HTTP request arrives
Express receives the request and runs the JSON body parser middleware (
express.json()), making req.body available as a parsed object to all subsequent handlers.Router matches the path
The relevant router file (e.g.
rutas.productos.mjs) matches the HTTP method and URL pattern and delegates to the appropriate controller function. Route parameters (:id) and query strings are accessible on the request object.Controller validates and orchestrates
The controller function checks that required fields are present, that numeric values are in range, and that array structures are well-formed. If validation fails it returns a
400 JSON error immediately. If validation passes it calls one or more model functions.Model executes SQL via pool
The model function runs a parameterized query against the Supabase PostgreSQL database using the shared
pg connection pool. Write operations that touch multiple tables (create product + images + variants) use a dedicated client with explicit BEGIN / COMMIT / ROLLBACK to guarantee atomicity.Static file serving
server.mjs registers three static directories before mounting the API routers, so every static asset is resolved before Express even looks at the API routes.
| Mount path | Source directory | Contents |
|---|---|---|
/recursos | src/public/recursos/ | Shared CSS, JavaScript bundles, and product images |
/admin | src/public/admin/ | Admin panel single-page HTML + its own JS/CSS |
/ | src/public/tienda/ | Customer-facing store HTML pages |
/recursos is declared first, asset paths like /recursos/css/admin.css and /recursos/imagenes/productos/*.webp resolve correctly regardless of which section of the site the browser is viewing.
Database connection
The entire application shares a singlepg.Pool instance exported from src/config/conexion.bd.mjs. Connection pooling means the server reuses open TCP connections to Supabase instead of opening a new one per query, which is critical for performance on a serverless-friendly cloud database.
DATABASE_URL environment variable holds the full Supabase connection string and is loaded at startup via dotenv. The ssl.rejectUnauthorized: false flag is required because Supabase uses a self-signed certificate chain for its pooler endpoint — it enables encrypted transport while skipping CA verification.
Every model file imports this single pool instance and calls pool.query() for simple reads or pool.connect() for transactions that need a dedicated client.
ES Modules
All source files use the.mjs extension and ES Module syntax (import / export). The project’s package.json also sets "type": "module" so Node.js treats every .js file as an ES module by default. This means:
import.meta.urlis used to derive__filenameand__dirname(which are not available natively in ESM).- All inter-module imports must include the full file extension (e.g.
./modelo.productos.mjs). - Dynamic
require()calls are not available; useimport()for conditional loading.