Overview
The application uses Socket.io v4.4.1 to provide real-time updates across all connected clients when permits are created, edited, approved, or cancelled. This ensures all users see changes immediately without requiring page refreshes.
Server Setup
Socket.io is initialized alongside the Express server:
const SocketIO = require ( 'socket.io' ),
io = SocketIO ( server );
io . on ( 'connection' , ( socket ) => {
console . log ( '¡Se ha detectado una conexión!' , socket . id );
// Event handlers registered here...
});
Source: src/index.js:86-92
Event Categories
Events are organized by permit type with consistent naming patterns:
Bebidas Alcohol sales permits
Eventos Special event permits
Publicidad Advertising permits
Each category supports the same event types:
nuevo-permiso: New permit created
edit-permiso: Permit edited
aprobate-permiso: Permit approved
cancel-permiso: Permit cancelled
delete: Permit deleted
Event Handlers
Bebidas (Alcohol Permits)
New Permit Event
socket . on ( 'bebidas:nuevo-permiso' , async ( data ) => {
let pool = require ( './database' );
await pool . query (
'SELECT * FROM permisos_bebidas WHERE codigo_permiso=?' ,
[ data . permiso ],
( err , result , fields ) => {
if ( err ) {
io . sockets . emit ( 'bebidas:nuevo-permiso' , err );
} else {
if ( result . length > 0 ) {
io . sockets . emit ( 'bebidas:nuevo-permiso' , result [ 0 ]);
} else {
io . sockets . emit ( 'bebidas:nuevo-permiso' , {
errno: '¡NOT FOUND!' ,
message: '¡Permiso no encontrado!' ,
code: 'ERRPERMIT'
});
}
}
}
);
pool . end ();
});
Source: src/index.js:120-142
Client emits bebidas:nuevo-permiso with { permiso: 'codigo' }
Server queries database for permit by code
Server broadcasts full permit data to all clients
All connected clients receive the new permit immediately
Edit, Approve, and Cancel Events
These events use a reusable socketEdit helper function:
function socketEdit ( socketDirection , table ) {
socket . on ( socketDirection , async ( data ) => {
let pool = require ( './database' ),
sql = 'SELECT * FROM ' + table + ' WHERE id=?' ;
await pool . query ( sql , [ data . permiso ], ( err , result , fields ) => {
if ( err ) {
io . sockets . emit ( socketDirection , err );
} else if ( result . length > 0 ) {
io . sockets . emit ( socketDirection , {
permiso: result [ 0 ],
id: data . id
});
} else {
io . sockets . emit ( socketDirection , {
errno: '¡NOT FOUND!' ,
message: '¡Permiso no encontrado!' ,
code: 'ERRPERMIT'
});
}
});
pool . end ();
});
}
// Usage:
socketEdit ( 'bebidas:edit-permiso' , 'permisos_bebidas' );
socketEdit ( 'bebidas:aprobate-permiso' , 'permisos_bebidas' );
socketEdit ( 'bebidas:cancel-permiso' , 'permisos_bebidas' );
Source: src/index.js:93-151
Delete Event
socket . on ( 'bebidas:delete' , ( data ) => {
io . sockets . emit ( 'bebidas:delete' , data );
});
Source: src/index.js:225-227
Delete events simply broadcast the received data without querying the database, as the deletion is handled by the route handler.
Eventos (Event Permits)
New Event Permit
socket . on ( 'eventos:nuevo-permiso' , async ( data ) => {
let pool = require ( './database' );
await pool . query (
'SELECT * FROM permisos_eventos WHERE codigo_permiso=?' ,
[ data . permiso ],
( err , result , fields ) => {
if ( err ) {
io . sockets . emit ( 'eventos:nuevo-permiso' , err );
} else {
if ( result . length > 0 ) {
io . sockets . emit ( 'eventos:nuevo-permiso' , result [ 0 ]);
} else {
io . sockets . emit ( 'eventos:nuevo-permiso' , {
errno: '¡NOT FOUND!' ,
message: '¡Permiso no encontrado!' ,
code: 'ERRPERMIT'
});
}
}
}
);
pool . end ();
});
Source: src/index.js:156-178
Other Event Operations
socketEdit ( 'eventos:edit-permiso' , 'permisos_eventos' );
socketEdit ( 'eventos:aprobate-permiso' , 'permisos_eventos' );
socketEdit ( 'eventos:cancel-permiso' , 'permisos_eventos' );
socket . on ( 'eventos:delete' , ( data ) => {
io . sockets . emit ( 'eventos:delete' , data );
});
Source: src/index.js:181-187, 233-235
Publicidad (Advertising Permits)
New Advertising Permit
socket . on ( 'publicidad:nuevo-permiso' , async ( data ) => {
let pool = require ( './database' );
await pool . query (
'SELECT * FROM permisos_publicidad WHERE codigo_permiso=?' ,
[ data . permiso ],
( err , result , fields ) => {
if ( err ) {
io . sockets . emit ( 'publicidad:nuevo-permiso' , err );
} else {
if ( result . length > 0 ) {
io . sockets . emit ( 'publicidad:nuevo-permiso' , result [ 0 ]);
} else {
io . sockets . emit ( 'publicidad:nuevo-permiso' , {
errno: '¡NOT FOUND!' ,
message: '¡Permiso no encontrado!' ,
code: 'ERRPERMIT'
});
}
}
}
);
pool . end ();
});
Source: src/index.js:192-214
Other Advertising Operations
socketEdit ( 'publicidad:edit-permiso' , 'permisos_publicidad' );
socketEdit ( 'publicidad:aprobate-permiso' , 'permisos_publicidad' );
socketEdit ( 'publicidad:cancel-permiso' , 'permisos_publicidad' );
socket . on ( 'publicidad:delete' , ( data ) => {
io . sockets . emit ( 'publicidad:delete' , data );
});
Source: src/index.js:217-223, 229-231
Event Payload Structures
Client to Server
New Permit
Edit/Approve/Cancel
Delete
{
permiso : 'codigo_permiso_value'
}
Server to Client
Success (New Permit)
Success (Edit/Approve/Cancel)
Error
Database Error
{
id : 123 ,
codigo_permiso : 'ABC-2024-001' ,
habilitacion : '2024-01-01' ,
vencimiento : '2024-12-31' ,
requisitor_nombre : 'Juan' ,
requisitor_apellido : 'Pérez' ,
// ... all permit fields
}
Client Integration
Clients connect to Socket.io and listen for events:
// Connect to Socket.io server
const socket = io ();
// Listen for new permits
socket . on ( 'bebidas:nuevo-permiso' , ( data ) => {
if ( data . errno ) {
console . error ( 'Error:' , data . message );
} else {
// Add new permit to UI
addPermitToTable ( data );
}
});
// Listen for permit updates
socket . on ( 'bebidas:edit-permiso' , ( data ) => {
if ( data . permiso ) {
updatePermitInTable ( data . permiso , data . id );
}
});
// Listen for deletions
socket . on ( 'bebidas:delete' , ( data ) => {
removePermitFromTable ( data . id );
});
// Emit events to server
socket . emit ( 'bebidas:nuevo-permiso' , { permiso: 'ABC-2024-001' });
Event Flow Diagram
Complete Event Reference
Event Payload (C→S) Payload (S→C) bebidas:nuevo-permiso{permiso: codigo}Full permit object bebidas:edit-permiso{permiso: id, id: elementId}{permiso: {...}, id: elementId}bebidas:aprobate-permiso{permiso: id, id: elementId}{permiso: {...}, id: elementId}bebidas:cancel-permiso{permiso: id, id: elementId}{permiso: {...}, id: elementId}bebidas:delete{id: permitId}{id: permitId}
Event Payload (C→S) Payload (S→C) eventos:nuevo-permiso{permiso: codigo}Full permit object eventos:edit-permiso{permiso: id, id: elementId}{permiso: {...}, id: elementId}eventos:aprobate-permiso{permiso: id, id: elementId}{permiso: {...}, id: elementId}eventos:cancel-permiso{permiso: id, id: elementId}{permiso: {...}, id: elementId}eventos:delete{id: permitId}{id: permitId}
Event Payload (C→S) Payload (S→C) publicidad:nuevo-permiso{permiso: codigo}Full permit object publicidad:edit-permiso{permiso: id, id: elementId}{permiso: {...}, id: elementId}publicidad:aprobate-permiso{permiso: id, id: elementId}{permiso: {...}, id: elementId}publicidad:cancel-permiso{permiso: id, id: elementId}{permiso: {...}, id: elementId}publicidad:delete{id: permitId}{id: permitId}
Broadcasting Strategy
The server uses io.sockets.emit() to broadcast to all connected clients including the sender:
io . sockets . emit ( 'bebidas:nuevo-permiso' , data );
This approach ensures all users see updates immediately, creating a real-time collaborative environment.
Alternative Broadcast Methods
All Clients (Current)
All Except Sender
Specific Client
io . sockets . emit ( 'event' , data );
// Sends to ALL clients including sender
Connection Lifecycle
Client Connects
Browser loads page with Socket.io client library
Handshake
Client establishes WebSocket connection to server
Connection Event
Server logs: ¡Se ha detectado una conexión! <socket-id>
Event Registration
Server registers all event listeners for this socket
Active Communication
Client and server exchange real-time events
Disconnect
Connection closed when client navigates away or loses connection
Error Handling
The Socket.io implementation handles errors in three scenarios:
if ( err ) {
io . sockets . emit ( 'bebidas:nuevo-permiso' , err );
}
Broadcasts the MySQL error object to all clients
io . sockets . emit ( 'bebidas:nuevo-permiso' , {
errno: '¡NOT FOUND!' ,
message: '¡Permiso no encontrado!' ,
code: 'ERRPERMIT'
});
Custom error when permit doesn’t exist
Socket.io automatically handles reconnection attempts and connection failures
The current implementation creates a new database connection for each event and closes it immediately: let pool = require ( './database' );
// ... query ...
pool . end ();
This pattern may cause connection overhead under high load. Consider using a persistent connection pool.
System Architecture Overall system design and request flow
Database Schema Structure of permit tables referenced by events