Architecture
The Sales Management System follows a layered Model-View-Controller (MVC) architecture pattern with clear separation of concerns.
System Overview
Architecture Pattern: MVC
The system implements a classic Model-View-Controller pattern adapted for RESTful APIs:
Model : Data access and business logic (src/model/)
View : JSON responses (no template engine)
Controller : Request handling and orchestration (src/controller/)
Directory Structure
src/
├── index.js # Application entry point
├── config.js # Configuration settings
├── routes/ # Route definitions
│ ├── routes.js # Main router
│ ├── categories.route.js
│ ├── products.route.js
│ ├── clients.route.js
│ └── sales.route.js
├── controller/ # Request handlers
│ ├── categories/
│ │ ├── createCategorie.js
│ │ ├── getCategorie.js
│ │ ├── getCategories.js
│ │ └── deleteCategorie.js
│ ├── products/
│ │ ├── createProduct.js
│ │ ├── getProduct.js
│ │ ├── getProducts.js
│ │ ├── updateProduct.js
│ │ └── deleteProduct.js
│ ├── clients/
│ │ ├── createClient.js
│ │ ├── getClient.js
│ │ ├── getClients.js
│ │ ├── updateClient.js
│ │ └── deleteClient.js
│ ├── sales/
│ │ ├── createSales.js
│ │ ├── getSale.js
│ │ ├── getSales.js
│ │ ├── updateSales.js
│ │ └── deleteSales.js
│ └── validation/ # Zod schema validators
│ ├── categorie.validation.js
│ ├── products.validation.js
│ ├── client.validation.js
│ └── sales.validation.js
└── model/ # Database queries
├── pool.js # Connection pool
├── database.sql # Schema definition
├── categorie.model.js
├── products.model.js
├── clients.model.js
└── sales.model.js
Request Flow
The system processes requests through a sequential pipeline:
1. Entry Point (src/index.js)
The Express application is initialized:
import express from "express"
import routes from "./routes/routes.js" ;
import config from "./config.js" ;
const app = express ();
app . use ( express . json ());
app . use ( express . urlencoded ())
app . use ( routes )
app . listen ( config . PORT , ( err ) => {
if ( err ) console . log ( err )
else {
console . log ( `Server on port http://localhost: ${ config . PORT } ` )
}
})
Key components:
Express middleware for JSON and URL-encoded body parsing
Central route registration
Server listening on configured port (default: 3000)
2. Routes Layer (src/routes/)
Routes define URL patterns and map them to controllers:
import { Router } from "express" ;
const routes = Router ();
import clients from "./clients.route.js"
import categories from "./categories.route.js"
import products from "./products.route.js"
import sales from "./sales.route.js"
routes . use ( sales )
routes . use ( products )
routes . use ( clients );
routes . use ( categories )
export default routes ;
Route organization:
Each resource (categories, products, clients, sales) has its own route file
RESTful URL patterns
HTTP method mapping (GET, POST, PATCH, DELETE)
3. Controller Layer (src/controller/)
Controllers handle request processing and orchestration:
import { validateCategorie } from "../validation/categorie.validation.js"
import { createCategorie } from "../../model/categorie.model.js"
const isEmpty = ( body ) => ! body || Object . keys ( body ). length == 0
const createCategorieController = async ( req , res ) => {
if ( isEmpty ( req . body ))
return res . status ( 400 ). json ({ error: true , msg: "request body is empty" })
const { name } = req . body ;
const validCategorie = await validateCategorie ( name )
if ( ! validCategorie )
return res . status ( 400 ). json ({ error: true , msg: "categorie name is invalid" })
const data = await createCategorie ( name )
if ( data . error )
return res . status ( 409 ). json ({ error: true , msg: "categorie name exist" })
return res . status ( 200 ). json ({ error: false , msg: "categorie was created sucessfully" })
}
export default createCategorieController ;
Controller responsibilities:
Extract request data (body, params, query)
Validate request body is not empty
Call validation layer
Invoke model methods
Format and return responses
Set appropriate HTTP status codes
4. Validation Layer (src/controller/validation/)
Validators use Zod for schema validation:
import z from "zod" ;
const schemmaName = z . string (). min ( 3 )
const schemmaId = z . int (). min ( 1 )
export const validateCategorie = async ( categorie ) => {
let result = await schemmaName . safeParseAsync ( categorie )
return result . success ;
}
export const validateId = async ( id ) => {
id = parseInt ( id );
const success = ( await schemmaId . safeParseAsync ( id )). success
return success
}
Validation rules:
Categories : name min 3 characters
Products :
name min 3 characters
description min 10 characters
price must be positive number
stock must be non-negative integer
Clients :
name min 3 characters
valid email format
Sales :
valid client UUID
valid product UUIDs
quantity must be positive
Zod’s safeParseAsync returns {success: boolean, data?: T, error?: ZodError} without throwing exceptions.
5. Model Layer (src/model/)
Models handle database queries and business logic:
import { pool } from "./pool.js" ;
export function getCategorie ( id ) {
return pool . query (
"SELECT * FROM categories WHERE id = ?" ,
id
). then (( row ) => {
if ( row [ 0 ]. length === 0 )
return { error: true , msg: "Categorie no exits" }
else
return { error: false , msg: "Categorie got sucessfully" , categorie: row [ 0 ][ 0 ] }
}). catch (( err ) => {
return { error: true , msg: "Query of getCategorie fail" }
})
}
export function createCategorie ( name ) {
return pool . query (
"INSERT INTO categories (name) VALUES (?)" ,
[ name ]
). then (() => {
return { error: false , msg: "categorie created sucessfully" }
}). catch (( err ) => {
return { error: true , msg: "Query of createCategories fail" }
})
}
export function deleteCategorie ( id ) {
if ( ! id ) return { error: true , msg: "the id field is missing" }
return pool . query (
`SELECT BIN_TO_UUID(id) as id FROM products WHERE category_id = ?` ,
[ id ]
). then (( value ) => {
if ( value [ 0 ]. length != 0 ) {
throw new Error ( JSON . stringify ({
error: true ,
msg: "there are products with this category" ,
productList: value [ 0 ]
}))
} else {
return pool . query (
"DELETE FROM categories WHERE id = ?" ,
[ id ]
)
}
}). then (( row ) => {
return { error: false , msg: "Categorie deleted successfully" };
}). catch (( err ) => {
return JSON . parse ( err . message )
})
}
Model responsibilities:
Execute SQL queries using connection pool
Handle UUID conversion (BIN_TO_UUID, UUID_TO_BIN)
Enforce business rules (e.g., foreign key constraints)
Return standardized response objects
Catch and format database errors
6. Database Connection Pool
The system uses mysql2 with connection pooling for efficiency:
import { createPool } from 'mysql2/promise' ;
import config from '../config.js' ;
export const pool = createPool ({
host: config . HOST_DATABASE ,
port: config . PORT_DATABASE ,
database: config . DATABASE_NAME ,
user: config . USER_DATABASE ,
password: config . PASSWORD_DATABASE
});
Connection pooling benefits:
Reuses connections instead of creating new ones
Automatically manages connection lifecycle
Improves performance under high load
Handles connection failures gracefully
Design Patterns
Repository Pattern
Models act as repositories, abstracting database access:
// Controller doesn't know SQL details
const categories = await getCategories ();
// Model handles the query
export function getCategories () {
return pool . query ( "SELECT * FROM categories" )
. then (( row ) => row [ 0 ])
. catch (( err ) => ({ error: true , msg: "Query failed" }))
}
Promise-based Async Flow
All database operations return promises:
// Async/await in controllers
const data = await createCategorie ( name );
if ( data . error ) return res . status ( 409 ). json ( data );
// Promise chains in models
return pool . query ( ... )
. then ( ... )
. catch ( ... )
Dependency Injection
Controllers import dependencies explicitly:
import { validateCategorie } from "../validation/categorie.validation.js"
import { createCategorie } from "../../model/categorie.model.js"
This makes testing easier and dependencies explicit.
Error Response Standardization
All responses follow a consistent format:
// Success response
{
"error" : false ,
"msg" : "Operation successful" ,
"data" : { ... }
}
// Error response
{
"error" : true ,
"msg" : "Error description" ,
"details" : [ ... ]
}
Data Flow Example
Create a category request flow:
1. Client sends POST /categories {"name": "bebidas"}
↓
2. Express parses JSON body
↓
3. Route matches POST /categories → createCategorieController
↓
4. Controller extracts {name} from body
↓
5. Controller calls validateCategorie("bebidas")
↓
6. Validation layer checks: string.min(3) ✓
↓
7. Controller calls createCategorie("bebidas")
↓
8. Model executes INSERT INTO categories (name) VALUES ('bebidas')
↓
9. MySQL stores record and returns success
↓
10. Model returns {error: false, msg: "..."}
↓
11. Controller formats response
↓
12. Express sends JSON: {"error": false, "msg": "categorie was created sucessfully"}
ES Modules
The entire codebase uses ES6 modules (import/export):
Benefits:
Modern JavaScript syntax
Static analysis and tree-shaking
Explicit dependencies
Native browser compatibility (future-proof)
Middleware Pipeline
Request
↓
app . use ( express . json ()) // Parse JSON bodies
↓
app . use ( express . urlencoded ()) // Parse URL-encoded bodies
↓
app . use ( routes ) // Route to controllers
↓
Controller validation & processing
↓
Response
Security Considerations
SQL Injection Prevention
All queries use parameterized statements :
// Safe - parameterized
pool . query ( "SELECT * FROM categories WHERE id = ?" , [ id ])
// Unsafe - string concatenation (NOT used)
// pool.query(`SELECT * FROM categories WHERE id = ${id}`)
Zod validates all inputs before database operations:
const validCategorie = await validateCategorie ( name )
if ( ! validCategorie )
return res . status ( 400 ). json ({ error: true , msg: "categorie name is invalid" })
Error Message Sanitization
Database errors are caught and returned with generic messages:
. catch (( err ) => {
return { error: true , msg: "Query of createCategories fail" }
})
Never expose raw database errors to clients in production. They may reveal schema details or SQL queries.
Connection Pooling
Reuses database connections instead of creating new ones per request.
Prepared Statements
mysql2 automatically caches prepared statements, improving performance for repeated queries.
Async/Await
Non-blocking I/O allows handling multiple requests concurrently.
Auto-reload in Development
{
"scripts" : {
"dev" : "node --watch src/index.js"
}
}
Node.js --watch flag automatically restarts on file changes.
Next Steps
Database Schema Understand the data model
API Reference Explore all endpoints
Error Handling Handle errors properly
Configuration Configure the application