Arquitectura Cliente-Servidor
Fundamentos de la comunicación web y cómo interactúan navegadores con servidores
Modelo Cliente-Servidor
La arquitectura cliente-servidor es un modelo de diseño de software donde las tareas se reparten entre proveedores de recursos o servicios (servidores) y demandantes de servicios (clientes).
- • Navegador web (Chrome, Firefox, Safari)
- • Aplicación móvil
- • Aplicación de escritorio
- • CLI tools
Función: Solicita recursos, muestra información, interactúa con el usuario
- • Servidor web (Node.js, Apache, Nginx)
- • Servidor de aplicaciones
- • Servidor de base de datos
- • Servidor de archivos
Función: Procesa peticiones, ejecuta lógica de negocio, almacena datos
Flujo de Comunicación
┌─────────────┐ ┌─────────────┐
│ CLIENTE │ │ SERVIDOR │
│ (Browser) │ │ (Node.js) │
└──────┬──────┘ └──────┬──────┘
│ │
│ 1. HTTP Request │
│ GET /api/users │
├───────────────────────────────────────>│
│ │
│ 2. Procesar │
│ petición │
│ 3. Consultar │
│ DB │
│ │
│ 4. HTTP Response │
│ 200 OK + JSON │
│<───────────────────────────────────────┤
│ │
│ 5. Renderizar datos │
│ │Arquitectura de 3 Capas
1. Capa de Presentación (Frontend)
Interfaz de usuario que muestra datos y captura inputs.
// React Component
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(data));
}, []);
return (
<div>
{users.map(user => (
<div key={user.id}>{user.name}</div>
))}
</div>
);
}2. Capa de Lógica de Negocio (Backend)
Procesa peticiones, aplica reglas de negocio y validaciones.
// Express API Route
app.get('/api/users', authenticate, async (req, res) => {
// Validar permisos
if (!req.user.canViewUsers) {
return res.status(403).json({ error: 'Forbidden' });
}
// Lógica de negocio
const users = await userService.getActiveUsers();
// Transformar datos
const sanitized = users.map(u => ({
id: u.id,
name: u.name,
email: u.email
// NO incluir password, tokens, etc
}));
res.json(sanitized);
});3. Capa de Datos (Database)
Almacena y recupera datos de forma persistente.
// User Service (con Prisma ORM)
class UserService {
async getActiveUsers() {
return await prisma.user.findMany({
where: {
isActive: true,
deletedAt: null
},
orderBy: { createdAt: 'desc' },
take: 100
});
}
async createUser(data) {
// Validaciones
if (!data.email || !data.password) {
throw new Error('Email y password requeridos');
}
// Hash password
const passwordHash = await bcrypt.hash(data.password, 10);
return await prisma.user.create({
data: {
...data,
passwordHash,
isActive: true
}
});
}
}Implicaciones de Seguridad
⚠️ Nunca confíes en el cliente
El cliente puede ser modificado, inspeccionado y manipulado. TODA validación y seguridad debe implementarse en el servidor.
// ❌ MAL: Validación solo en cliente
// Frontend
if (user.role === 'admin') {
showDeleteButton(); // El usuario puede modificar esto en DevTools
}
// ✅ BIEN: Validación en servidor
// Backend
app.delete('/api/users/:id', authenticate, (req, res) => {
if (req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
// Proceder con eliminación
});🔒 Principio de separación
- • Frontend solo debe manejar UI/UX
- • Backend maneja autenticación, autorización y lógica crítica
- • Base de datos solo accesible desde backend (nunca directamente desde cliente)
- • Secrets y API keys NUNCA en código frontend
Siguiente Paso
Aprende sobre HTTP, el protocolo que hace posible esta comunicación
HTTP: El Protocolo de la Web→