JSON en APIs REST: Guía Completa para Desarrolladores
Aprende a diseñar y consumir APIs REST con JSON. Incluye mejores prácticas, ejemplos en Node.js, Python y autenticación.
Big JSON Team
• Technical WriterExpert in JSON data manipulation, API development, and web technologies. Passionate about creating tools that make developers' lives easier.
# JSON en APIs REST: Guía Completa para Desarrolladores
JSON es el formato estándar para APIs REST modernas. Esta guía completa te enseñará cómo diseñar, implementar y consumir APIs REST usando JSON.
¿Por Qué JSON para APIs REST?
Ventajas de JSON en APIs
- ✅ Ligero y eficiente - Menos bytes que XML
- ✅ Fácil de parsear - Soporte nativo en todos los lenguajes
- ✅ Legible para humanos - Fácil de depurar
- ✅ Tipado flexible - Soporta estructuras complejas
- ✅ Ampliamente adoptado - Estándar de la industria
Principios de API REST
Métodos HTTP
GET - Obtener recursos
POST - Crear recursos
PUT - Actualizar recursos (reemplazo completo)
PATCH - Actualizar recursos (parcial)
DELETE - Eliminar recursos
Códigos de Estado HTTP
200 OK - Éxito general
201 Created - Recurso creado
204 No Content - Éxito sin contenido
400 Bad Request - Datos inválidos
401 Unauthorized - No autenticado
403 Forbidden - No autorizado
404 Not Found - Recurso no encontrado
422 Unprocessable - Validación fallida
500 Internal Error - Error del servidor
Estructura de Respuestas JSON
Respuesta Exitosa Simple
{
"id": 1,
"nombre": "Ana García",
"email": "ana@ejemplo.com",
"activo": true
}
Respuesta con Metadatos
{
"status": "success",
"data": {
"id": 1,
"nombre": "Ana García",
"email": "ana@ejemplo.com"
},
"meta": {
"timestamp": "2024-01-15T10:30:00Z",
"version": "1.0"
}
}
Respuesta de Error
{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"message": "Datos de entrada inválidos",
"details": [
{
"field": "email",
"message": "Formato de email inválido"
},
{
"field": "edad",
"message": "Debe ser mayor que 0"
}
]
}
}
Respuesta Paginada
{
"status": "success",
"data": [
{"id": 1, "nombre": "Usuario 1"},
{"id": 2, "nombre": "Usuario 2"},
{"id": 3, "nombre": "Usuario 3"}
],
"pagination": {
"page": 1,
"perPage": 20,
"total": 150,
"totalPages": 8,
"hasNext": true,
"hasPrev": false
},
"links": {
"self": "/api/usuarios?page=1",
"next": "/api/usuarios?page=2",
"last": "/api/usuarios?page=8"
}
}
API REST con Node.js/Express
Configuración Básica
const express = require('express');
const app = express();
// Middleware para parsear JSON
app.use(express.json());
// Middleware para CORS
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(Servidor corriendo en puerto ${PORT});
});
Endpoints CRUD Completos
const express = require('express');
const router = express.Router();
// Base de datos simulada
let usuarios = [
{ id: 1, nombre: 'Ana García', email: 'ana@ejemplo.com', edad: 28 },
{ id: 2, nombre: 'Carlos López', email: 'carlos@ejemplo.com', edad: 35 }
];
// GET - Obtener todos los usuarios
router.get('/usuarios', (req, res) => {
const { page = 1, limit = 10, buscar } = req.query;
let resultado = usuarios;
// Filtrar por búsqueda
if (buscar) {
resultado = resultado.filter(u =>
u.nombre.toLowerCase().includes(buscar.toLowerCase())
);
}
// Paginación
const inicio = (page - 1) limit;
const fin = inicio + parseInt(limit);
const paginado = resultado.slice(inicio, fin);
res.json({
status: 'success',
data: paginado,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: resultado.length
}
});
});
// GET - Obtener usuario por ID
router.get('/usuarios/:id', (req, res) => {
const usuario = usuarios.find(u => u.id === parseInt(req.params.id));
if (!usuario) {
return res.status(404).json({
status: 'error',
error: {
code: 'NOT_FOUND',
message: 'Usuario no encontrado'
}
});
}
res.json({
status: 'success',
data: usuario
});
});
// POST - Crear usuario
router.post('/usuarios', (req, res) => {
const { nombre, email, edad } = req.body;
// Validación
const errores = [];
if (!nombre || nombre.length < 2) {
errores.push({ field: 'nombre', message: 'Nombre requerido (mín 2 caracteres)' });
}
if (!email || !email.includes('@')) {
errores.push({ field: 'email', message: 'Email válido requerido' });
}
if (!edad || edad < 0) {
errores.push({ field: 'edad', message: 'Edad debe ser mayor que 0' });
}
if (errores.length > 0) {
return res.status(400).json({
status: 'error',
error: {
code: 'VALIDATION_ERROR',
message: 'Datos inválidos',
details: errores
}
});
}
// Crear usuario
const nuevoUsuario = {
id: usuarios.length + 1,
nombre,
email,
edad
};
usuarios.push(nuevoUsuario);
res.status(201).json({
status: 'success',
data: nuevoUsuario
});
});
// PUT - Actualizar usuario completo
router.put('/usuarios/:id', (req, res) => {
const index = usuarios.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) {
return res.status(404).json({
status: 'error',
error: { code: 'NOT_FOUND', message: 'Usuario no encontrado' }
});
}
const { nombre, email, edad } = req.body;
usuarios[index] = {
id: parseInt(req.params.id),
nombre,
email,
edad
};
res.json({
status: 'success',
data: usuarios[index]
});
});
// PATCH - Actualizar usuario parcial
router.patch('/usuarios/:id', (req, res) => {
const index = usuarios.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) {
return res.status(404).json({
status: 'error',
error: { code: 'NOT_FOUND', message: 'Usuario no encontrado' }
});
}
// Actualizar solo campos proporcionados
usuarios[index] = {
...usuarios[index],
...req.body,
id: usuarios[index].id // Preservar ID
};
res.json({
status: 'success',
data: usuarios[index]
});
});
// DELETE - Eliminar usuario
router.delete('/usuarios/:id', (req, res) => {
const index = usuarios.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) {
return res.status(404).json({
status: 'error',
error: { code: 'NOT_FOUND', message: 'Usuario no encontrado' }
});
}
usuarios.splice(index, 1);
res.status(204).send();
});
module.exports = router;
API REST con Python/Flask
Configuración Básica
from flask import Flask, request, jsonify
from flask_cors import CORS
app = Flask(__name__)
CORS(app) # Habilitar CORS
# Base de datos simulada
usuarios = [
{"id": 1, "nombre": "Ana García", "email": "ana@ejemplo.com", "edad": 28},
{"id": 2, "nombre": "Carlos López", "email": "carlos@ejemplo.com", "edad": 35}
]
if __name__ == '__main__':
app.run(debug=True, port=5000)
Endpoints CRUD
from flask import Flask, request, jsonify
app = Flask(__name__)
# GET - Listar usuarios
@app.route('/api/usuarios', methods=['GET'])
def obtener_usuarios():
page = request.args.get('page', 1, type=int)
limit = request.args.get('limit', 10, type=int)
buscar = request.args.get('buscar', '', type=str)
# Filtrar
resultado = usuarios
if buscar:
resultado = [u for u in usuarios if buscar.lower() in u['nombre'].lower()]
# Paginar
inicio = (page - 1) limit
fin = inicio + limit
paginado = resultado[inicio:fin]
return jsonify({
'status': 'success',
'data': paginado,
'pagination': {
'page': page,
'limit': limit,
'total': len(resultado)
}
})
# GET - Obtener usuario por ID
@app.route('/api/usuarios/<int:id>', methods=['GET'])
def obtener_usuario(id):
usuario = next((u for u in usuarios if u['id'] == id), None)
if not usuario:
return jsonify({
'status': 'error',
'error': {
'code': 'NOT_FOUND',
'message': 'Usuario no encontrado'
}
}), 404
return jsonify({
'status': 'success',
'data': usuario
})
# POST - Crear usuario
@app.route('/api/usuarios', methods=['POST'])
def crear_usuario():
datos = request.get_json()
# Validación
errores = []
if not datos.get('nombre') or len(datos.get('nombre', '')) < 2:
errores.append({'field': 'nombre', 'message': 'Nombre requerido (mín 2 caracteres)'})
if not datos.get('email') or '@' not in datos.get('email', ''):
errores.append({'field': 'email', 'message': 'Email válido requerido'})
if not datos.get('edad') or datos.get('edad', 0) < 0:
errores.append({'field': 'edad', 'message': 'Edad debe ser mayor que 0'})
if errores:
return jsonify({
'status': 'error',
'error': {
'code': 'VALIDATION_ERROR',
'message': 'Datos inválidos',
'details': errores
}
}), 400
# Crear usuario
nuevo_usuario = {
'id': len(usuarios) + 1,
'nombre': datos['nombre'],
'email': datos['email'],
'edad': datos['edad']
}
usuarios.append(nuevo_usuario)
return jsonify({
'status': 'success',
'data': nuevo_usuario
}), 201
# PUT - Actualizar usuario
@app.route('/api/usuarios/<int:id>', methods=['PUT'])
def actualizar_usuario(id):
usuario = next((u for u in usuarios if u['id'] == id), None)
if not usuario:
return jsonify({
'status': 'error',
'error': {'code': 'NOT_FOUND', 'message': 'Usuario no encontrado'}
}), 404
datos = request.get_json()
usuario.update({
'nombre': datos['nombre'],
'email': datos['email'],
'edad': datos['edad']
})
return jsonify({
'status': 'success',
'data': usuario
})
# DELETE - Eliminar usuario
@app.route('/api/usuarios/<int:id>', methods=['DELETE'])
def eliminar_usuario(id):
global usuarios
usuarios = [u for u in usuarios if u['id'] != id]
return '', 204
Consumir APIs REST
JavaScript/Fetch
// GET Request
async function obtenerUsuarios() {
try {
const response = await fetch('https://api.ejemplo.com/usuarios');
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status});
}
const data = await response.json();
console.log(data);
return data;
} catch (error) {
console.error('Error:', error);
}
}
// POST Request
async function crearUsuario(usuario) {
try {
const response = await fetch('https://api.ejemplo.com/usuarios', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${token}
},
body: JSON.stringify(usuario)
});
const data = await response.json();
if (!response.ok) {
console.error('Error:', data.error);
return null;
}
return data;
} catch (error) {
console.error('Error de red:', error);
}
}
// PUT Request
async function actualizarUsuario(id, usuario) {
const response = await fetch(https://api.ejemplo.com/usuarios/${id}, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(usuario)
});
return response.json();
}
// DELETE Request
async function eliminarUsuario(id) {
const response = await fetch(https://api.ejemplo.com/usuarios/${id}, {
method: 'DELETE'
});
return response.status === 204;
}
// Uso
const nuevoUsuario = {
nombre: 'María Rodríguez',
email: 'maria@ejemplo.com',
edad: 31
};
crearUsuario(nuevoUsuario).then(resultado => {
console.log('Usuario creado:', resultado);
});
Python/Requests
import requests
# GET Request
def obtener_usuarios():
try:
response = requests.get('https://api.ejemplo.com/usuarios')
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f'Error: {e}')
return None
# POST Request
def crear_usuario(usuario):
try:
response = requests.post(
'https://api.ejemplo.com/usuarios',
json=usuario,
headers={'Authorization': f'Bearer {token}'}
)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f'Error: {e}')
return None
# PUT Request
def actualizar_usuario(id, usuario):
response = requests.put(
f'https://api.ejemplo.com/usuarios/{id}',
json=usuario
)
return response.json()
# DELETE Request
def eliminar_usuario(id):
response = requests.delete(f'https://api.ejemplo.com/usuarios/{id}')
return response.status_code == 204
# Uso
nuevo_usuario = {
'nombre': 'Pedro Sánchez',
'email': 'pedro@ejemplo.com',
'edad': 42
}
resultado = crear_usuario(nuevo_usuario)
print(f'Usuario creado: {resultado}')
Autenticación
JWT (JSON Web Tokens)
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET || 'tu-secreto-aqui';
// Generar token
function generarToken(usuario) {
const payload = {
id: usuario.id,
email: usuario.email,
rol: usuario.rol
};
return jwt.sign(payload, SECRET, { expiresIn: '24h' });
}
// Middleware de autenticación
function autenticar(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({
status: 'error',
error: { code: 'NO_TOKEN', message: 'Token no proporcionado' }
});
}
jwt.verify(token, SECRET, (err, usuario) => {
if (err) {
return res.status(403).json({
status: 'error',
error: { code: 'INVALID_TOKEN', message: 'Token inválido' }
});
}
req.usuario = usuario;
next();
});
}
// Login endpoint
app.post('/api/login', (req, res) => {
const { email, password } = req.body;
// Verificar credenciales (ejemplo simplificado)
const usuario = usuarios.find(u => u.email === email);
if (!usuario || usuario.password !== password) {
return res.status(401).json({
status: 'error',
error: { code: 'INVALID_CREDENTIALS', message: 'Credenciales inválidas' }
});
}
const token = generarToken(usuario);
res.json({
status: 'success',
data: {
token,
usuario: {
id: usuario.id,
nombre: usuario.nombre,
email: usuario.email
}
}
});
});
// Ruta protegida
app.get('/api/perfil', autenticar, (req, res) => {
res.json({
status: 'success',
data: req.usuario
});
});
Mejores Prácticas
1. Versionado de API
// Opción 1: URL path
app.use('/api/v1/usuarios', routerV1);
app.use('/api/v2/usuarios', routerV2);
// Opción 2: Header
app.use((req, res, next) => {
const version = req.headers['api-version'] || 'v1';
req.apiVersion = version;
next();
});
2. Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 60 * 1000, // 15 minutos
max: 100, // Límite de 100 requests
message: {
status: 'error',
error: {
code: 'RATE_LIMIT_EXCEEDED',
message: 'Demasiadas peticiones, intenta de nuevo más tarde'
}
}
});
app.use('/api/', limiter);
3. Compresión
const compression = require('compression');
app.use(compression());
4. Logging
const morgan = require('morgan');
app.use(morgan('combined'));
5. Manejo de Errores Global
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
status: 'error',
error: {
code: err.code || 'INTERNAL_ERROR',
message: err.message || 'Error interno del servidor'
}
});
});
Conclusión
JSON es el formato perfecto para APIs REST modernas. Siguiendo estas mejores prácticas, puedes crear APIs robustas, escalables y fáciles de mantener.
Puntos Clave
- Usa códigos de estado HTTP apropiados
- Estructura respuestas consistentemente
- Implementa paginación para listas grandes
- Valida datos de entrada
- Usa autenticación JWT
- Implementa versionado de API
- Documenta tu API
¡Crea APIs REST profesionales con JSON!
Artículos Relacionados
Python y JSON: Guía Completa del Módulo json
Aprende a trabajar con JSON en Python usando el módulo json. Incluye parsing, serialización, archivos JSON y mejores prácticas.
JavaScript y JSON: Guía Completa de JSON.parse() y JSON.stringify()
Domina el trabajo con JSON en JavaScript. Aprende JSON.parse(), JSON.stringify() y mejores prácticas para aplicaciones web modernas.
Entendiendo JSON Schema: Validación y Documentación de Datos
Aprende a usar JSON Schema para validar y documentar tus estructuras JSON. Guía completa con ejemplos prácticos en JavaScript y Python.