APIs REST
Aprende los principios de diseño de APIs RESTful, métodos HTTP, endpoints y códigos de estado
¿Qué es REST?
REST (Transferencia de Estado Representacional) es un estilo arquitectónico para diseñar aplicaciones en red. Creado por Roy Fielding en 2000, REST se ha convertido en el patrón de diseño dominante para APIs web debido a su simplicidad, escalabilidad y uso de protocolos HTTP estándar.
Una API RESTful es aquella que cumple con los principios REST, proporcionando una interfaz uniforme para interactuar con recursos.
Principios de REST
🔗 Interfaz Uniforme
Forma consistente de interactuar con recursos usando métodos HTTP y URLs estándar.
🚫 Sin Estado
Cada petición contiene toda la información necesaria. El servidor no almacena el estado del cliente.
📦 Cacheable
Las respuestas pueden ser cacheadas para mejorar el rendimiento y reducir la carga del servidor.
📊 Sistema en Capas
El cliente no puede distinguir si está conectado directamente al servidor o a través de intermediarios.
🖥️ Cliente-Servidor
Separación de responsabilidades entre cliente (UI) y servidor (datos).
📝 Basado en Recursos
Todo es un recurso identificado por URIs. Los recursos tienen representaciones (JSON/XML).
Estructura de URL RESTful
Las APIs REST usan URLs descriptivas y jerárquicas para representar recursos:
// Estructura de URL de Recurso
https://api.example.com/v1/users // Colección de usuarios
https://api.example.com/v1/users/123 // Usuario único (ID: 123)
https://api.example.com/v1/users/123/posts // Posts del usuario
https://api.example.com/v1/users/123/posts/456 // Post específico
// Componentes de URL:
// Protocolo: https://
// Host: api.example.com
// Versión: /v1
// Recurso: /users
// Identificador: /123
// Sub-recurso: /posts
✅ Buenas Prácticas de URL:
- • Usar sustantivos, no verbos:
/usersno/getUsers - • Usar sustantivos en plural:
/usersno/user - • Usar minúsculas:
/usersno/Users - • Usar guiones para palabras múltiples:
/user-profilesno/userProfiles
Métodos HTTP en Detalle
// GET - Recuperar todos los usuarios
const response = await fetch('https://api.example.com/users');
const users = await response.json();
// GET - Recuperar un usuario único
const user = await fetch('https://api.example.com/users/123');
const userData = await user.json();
// GET con parámetros de consulta (filtrado/paginación)
const filteredUsers = await fetch(
'https://api.example.com/users?role=admin&page=1&limit=10'
);
// Respuesta: 200 OK
{
"data": [
{ "id": 1, "name": "Juan Pérez", "role": "admin" }
],
"pagination": { "page": 1, "limit": 10, "total": 45 }
}
// POST - Crear nuevo usuario
const newUser = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({
name: 'Jane Smith',
email: 'jane@example.com',
role: 'user'
})
});
const created = await newUser.json();
// Respuesta: 201 Created
{
"id": 124,
"name": "Jane Smith",
"email": "jane@example.com",
"role": "user",
"createdAt": "2024-01-15T10:30:00Z"
}
// PUT - Reemplazar usuario completo (todos los campos requeridos)
const updatedUser = await fetch('https://api.example.com/users/124', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({
name: 'Jane Doe', // Cambiado
email: 'jane@example.com', // Igual
role: 'admin' // Cambiado
})
});
// Respuesta: 200 OK
{
"id": 124,
"name": "Jane Doe",
"email": "jane@example.com",
"role": "admin",
"updatedAt": "2024-01-15T11:00:00Z"
}
// PATCH - Actualizar solo campos específicos
const patchedUser = await fetch('https://api.example.com/users/124', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({
role: 'moderator' // Solo actualizando rol
})
});
// Respuesta: 200 OK
{
"id": 124,
"name": "Jane Doe",
"email": "jane@example.com",
"role": "moderator",
"updatedAt": "2024-01-15T11:30:00Z"
}
// DELETE - Eliminar usuario
const deleteResponse = await fetch('https://api.example.com/users/124', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer token123'
}
});
// Respuesta: 204 No Content (cuerpo vacío)
// O
// Respuesta: 200 OK
{
"message": "Usuario eliminado exitosamente",
"deletedId": 124
}
Códigos de Estado HTTP
Los códigos de estado indican el resultado de una petición HTTP:
✅ 2xx Éxito
200 OK - Petición exitosa201 Created - Recurso creado204 No Content - Éxito, sin cuerpo202 Accepted - Procesamiento en cola↪️ 3xx Redirección
301 Moved Permanently - Nueva URL302 Found - Redirección temporal304 Not Modified - Usar caché307 Temporary Redirect⚠️ 4xx Errores del Cliente
400 Bad Request - Sintaxis inválida401 Unauthorized - Autenticación requerida403 Forbidden - Sin permiso404 Not Found - Recurso faltante422 Unprocessable - Error de validación429 Too Many Requests - Limitación de tasa❌ 5xx Errores del Servidor
500 Internal Error - Error del servidor502 Bad Gateway - Upstream inválido503 Unavailable - Servidor caído504 Gateway Timeout - Tiempo de espera agotadoCliente API REST Completo
Construyendo una clase de cliente API reutilizable:
// Clase de Cliente API REST
class ApiClient {
constructor(baseUrl, token = null) {
this.baseUrl = baseUrl;
this.token = token;
}
setToken(token) {
this.token = token;
}
getHeaders() {
const headers = {
'Content-Type': 'application/json',
};
if (this.token) {
headers['Authorization'] = `Bearer ${this.token}`;
}
return headers;
}
async request(method, endpoint, data = null) {
const url = `${this.baseUrl}${endpoint}`;
const options = {
method,
headers: this.getHeaders(),
};
if (data && ['POST', 'PUT', 'PATCH'].includes(method)) {
options.body = JSON.stringify(data);
}
try {
const response = await fetch(url, options);
const contentType = response.headers.get('content-type');
let responseData;
if (contentType?.includes('application/json')) {
responseData = await response.json();
} else {
responseData = await response.text();
}
if (!response.ok) {
throw {
status: response.status,
message: responseData.message || 'Petición fallida',
data: responseData
};
}
return responseData;
} catch (error) {
console.error(`Error de API [${method}] ${endpoint}:`, error);
throw error;
}
}
// Métodos de conveniencia
get(endpoint) { return this.request('GET', endpoint); }
post(endpoint, data) { return this.request('POST', endpoint, data); }
put(endpoint, data) { return this.request('PUT', endpoint, data); }
patch(endpoint, data) { return this.request('PATCH', endpoint, data); }
delete(endpoint) { return this.request('DELETE', endpoint); }
}
// Ejemplo de Uso
const api = new ApiClient('https://api.example.com/v1');
api.setToken('tu-jwt-token');
// Operaciones CRUD
async function userOperations() {
// Crear
const newUser = await api.post('/users', {
name: 'Juan Pérez',
email: 'juan@example.com'
});
// Leer
const users = await api.get('/users');
const user = await api.get(`/users/${newUser.id}`);
// Actualizar
const updated = await api.patch(`/users/${newUser.id}`, {
name: 'Juan Martínez'
});
// Eliminar
await api.delete(`/users/${newUser.id}`);
}
userOperations();
Parámetros de Consulta
Usa parámetros de consulta para filtrado, ordenamiento y paginación:
// Construyendo URLs con Parámetros de Consulta
class QueryBuilder {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.params = new URLSearchParams();
}
page(page, limit = 10) {
this.params.set('page', page);
this.params.set('limit', limit);
return this;
}
filter(field, value) {
this.params.set(field, value);
return this;
}
sort(field, order = 'asc') {
this.params.set('sort', field);
this.params.set('order', order);
return this;
}
search(query) {
this.params.set('q', query);
return this;
}
fields(fieldList) {
this.params.set('fields', fieldList.join(','));
return this;
}
build() {
const queryString = this.params.toString();
return queryString ? `${this.baseUrl}?${queryString}` : this.baseUrl;
}
}
// Uso
const url = new QueryBuilder('https://api.example.com/users')
.page(2, 20)
.filter('role', 'admin')
.sort('createdAt', 'desc')
.search('juan')
.fields(['id', 'name', 'email'])
.build();
console.log(url);
// https://api.example.com/users?page=2&limit=20&role=admin&sort=createdAt&order=desc&q=juan&fields=id,name,email
Estándares de Formato de Respuesta
// Estructura de Respuesta API Consistente
// Respuesta de Éxito
{
"success": true,
"data": {
"id": 123,
"name": "Juan Pérez",
"email": "juan@example.com"
},
"meta": {
"timestamp": "2024-01-15T10:30:00Z"
}
}
// Respuesta de Colección con Paginación
{
"success": true,
"data": [
{ "id": 1, "name": "Juan" },
{ "id": 2, "name": "María" }
],
"pagination": {
"page": 1,
"limit": 10,
"total": 100,
"totalPages": 10,
"hasNext": true,
"hasPrev": false
}
}
// Respuesta de Error
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Datos de entrada inválidos",
"details": [
{ "field": "email", "message": "Formato de email inválido" },
{ "field": "age", "message": "Debe ser un número positivo" }
]
}
}
💡 Mejores Prácticas de API REST
- ✓ Usa métodos HTTP apropiados - GET para leer, POST para crear, etc.
- ✓ Retorna códigos de estado apropiados - 201 para creado, 404 para no encontrado
- ✓ Versiona tu API - Usa /v1/, /v2/ en URLs
- ✓ Usa JSON para datos - Formato estándar para APIs web
- ✓ Implementa paginación - No retornes resultados ilimitados
- ✓ Documenta tu API - Usa OpenAPI/Swagger