← Volver al Blog

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 Team16 min de lecturaDesarrollo
B

Big JSON Team

Technical Writer

Expert in JSON data manipulation, API development, and web technologies. Passionate about creating tools that make developers' lives easier.

16 min lectura

# 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!

Share:

Artículos Relacionados

Read in English