REST API design Preguntas de entrevista
Las entrevistas de diseño de API REST evalúan tu capacidad para construir APIs web escalables, mantenibles y fáciles de usar. Estas entrevistas son comunes para roles de backend, full-stack e ingeniería de plataforma, especialmente en niveles senior. Los entrevistadores evalúan tu comprensión de los principios REST, modelado de recursos, manejo de errores, paginación, versionado y seguridad. Te enfrentarás tanto a discusiones conceptuales como a desafíos prácticos de codificación.
Lo que cubren las entrevistas de REST API design
Principios y Restricciones RESTful
Comprensión de la falta de estado, interfaz uniforme, identificación de recursos y cómo usar correctamente los métodos HTTP (GET, POST, PUT, DELETE, PATCH) y códigos de estado.
Modelado y Nomenclatura de Recursos
Diseño de patrones URI intuitivos para recursos anidados, sustantivos en plural, filtrado, ordenamiento y evitar verbos en los endpoints.
Paginación, Filtrado y Versionado
Implementación de paginación basada en cursor vs offset, estrategias de filtrado/ordenamiento y enfoques de versionado por URL vs cabecera.
Manejo de Errores y Seguridad
Diseño de estructuras consistentes de respuesta de error, uso adecuado de códigos de estado HTTP y prácticas comunes de seguridad como autenticación, limitación de tasa y validación de entrada.
Ejemplos de preguntas de entrevista sobre REST API design
- Diseña una API REST para un sistema de artículos de noticias. ¿Cómo modelarías los recursos y endpoints para artículos, comentarios y etiquetas?Lo que cubre una buena respuesta
- Recursos principales: articles, comments, tags
- Endpoints RESTful para CRUD de articles y comentarios anidados
- Relación many-to-many entre articles y tags mediante un recurso intermedio
- Uso de query parameters para filtrar por tags o fecha
- Posibilidad de incluir recursos relacionados usando ?include=comments,author
Ver respuesta de ejemplo
Para modelar una API REST de artículos de noticias, definiría los recursos articles, comments y tags. Los endpoints principales serían: GET /articles (listar), POST /articles (crear), GET /articles/:id (detalle), PUT /articles/:id (actualizar) y DELETE /articles/:id (eliminar). Los comentarios son un subrecurso de articles, con endpoints como GET /articles/:id/comments y POST /articles/:id/comments. Las etiquetas (tags) tienen una relación muchos a muchos con articles, por lo que puedo modelarlas como un recurso independiente (GET /tags) y permitir la asignación mediante el cuerpo del artículo o un endpoint separado (POST /articles/:id/tags). Para evitar anidamiento excesivo, prefiero usar query parameters como ?tag=deportes en GET /articles en lugar de rutas anidadas profundas. También considero incluir recursos relacionados mediante query parameter (ej. ?include=author,comments) para reducir el número de peticiones. La consistencia y la facilidad de uso son clave; seguir convenciones RESTful estándar y usar HTTP status codes adecuados.
- Dada una API CRUD de usuarios, ¿cómo implementarías eliminaciones suaves y la recuperación de usuarios activos y eliminados?Lo que cubre una buena respuesta
- Agregar columna deleted_at (timestamp nullable) a la tabla de usuarios
- Modificar GET /users para filtrar por deleted_at IS NULL (activos)
- Añadir query parameter opcional ?include_deleted para listar todos
- Crear endpoint POST /users/:id/restore para eliminar suave inversa
- Actualizar DELETE /users/:id para establecer deleted_at en lugar de borrar
Ver respuesta de ejemplo
Para implementar eliminaciones suaves (soft delete) en una API CRUD de usuarios, añado una columna deleted_at de tipo timestamp que es NULL cuando el usuario está activo y tiene un valor cuando ha sido eliminado. El endpoint GET /users por defecto devuelve solo usuarios con deleted_at IS NULL. Si el cliente desea incluir eliminados, uso un query parameter opcional como ?include_deleted=true. El endpoint DELETE /users/:id cambia a un PUT lógico que establece deleted_at = NOW(). Para restaurar, creo un endpoint POST /users/:id/restore que pone deleted_at a NULL. Esto preserva los datos y permite auditorías. Las consultas deben tener índices apropiados en deleted_at para mantener el rendimiento. Como trade-off, se debe considerar la retención de datos y posibles conflictos con restricciones de unicidad; una alternativa es usar un campo booleano is_deleted, pero con timestamp se obtiene más información.
- ¿Cómo diseñarías la paginación para una gran colección de tweets? Compara la paginación por offset vs basada en cursor y elige una.Lo que cubre una buena respuesta
- Paginación basada en cursor (cursor-based) usando id o timestamp
- Evita duplicados y omisiones en inserciones concurrentes
- No requiere offset, mejor rendimiento en grandes conjuntos
- Ofrece next_cursor en la respuesta, cliente lo usa en la siguiente petición
- Trade-off: no permite saltar a páginas arbitrarias, pero ideal para scroll infinito
Ver respuesta de ejemplo
Para paginar una gran colección de tweets, recomiendo la paginación basada en cursor (cursor-based) en lugar de offset. La paginación por offset es frágil: si se insertan nuevos tweets mientras se navega, se pueden omitir o duplicar elementos. Además, el offset requiere escanear filas y es ineficiente en tablas grandes. La paginación por cursor usa un campo estable como el id del tweet (ej. creciente o timestamp) y filtra con WHERE id < cursor ORDER BY id DESC LIMIT 20. Proporciono el cursor de la última página en la respuesta (ej. next_cursor). El cliente lo usa en el siguiente request como ?cursor=abc. Esto garantiza consistencia y buen rendimiento con el índice. El único inconveniente es que no se puede acceder directamente a una página específica (ej. página 5), pero para aplicaciones sociales con scroll infinito es la opción óptima.
- Diseña una API que permita a los clientes solicitar campos específicos (sparse fieldsets) e incluir recursos relacionados (por ejemplo, publicaciones con detalles del autor).Lo que cubre una buena respuesta
- Usar query parameters fields[tipo] para sparse fieldsets (ej. ?fields[posts]=title,body)
- Usar query parameter include para recursos relacionados (ej. ?include=author)
- Implementar lógica del lado del servidor para filtrar atributos y cargar relaciones
- Estructurar respuesta coherente siguiendo JSON:API o similar
- Considerar caché y complejidad adicional
Ver respuesta de ejemplo
Para permitir que los clientes soliciten campos específicos y recursos relacionados, uso los query parameters fields e include siguiendo convenciones como JSON:API. Por ejemplo: GET /posts?fields[posts]=title,body&include=author&fields[author]=name. El servidor interpreta fields[posts] para devolver solo los atributos solicitados del recurso principal y fields[author] para el recurso incluido. El parámetro include especifica qué relaciones cargar eager loading. En la respuesta, la estructura debe separar los datos primarios de los incluidos (included). Esto reduce el tamaño del payload y mejora la eficiencia. Sin embargo, añade complejidad al servidor para parsear y aplicar filtros, y puede afectar la caché. Es importante documentar claramente los campos disponibles y las relaciones permitidas. Alternativas más simples incluyen devolver todos los campos y permitir solo include, pero sparse fieldsets proporcionan mayor granularidad.
- ¿Cómo versionarías una API REST? Explica los pros y contras del versionado por URI vs versionado por cabecera de solicitud personalizada.Lo que cubre una buena respuesta
- Versionado por URI: ej. /v1/usuarios, fácil de implementar y depurar
- Versionado por cabecera: Accept: application/vnd.api.v1+json, URLs limpias
- URI es más visible y permite coexistencia de versiones en caché
- Cabecera requiere acuerdos previos y no es evidente en la URL
- Recomendación: usar URI para APIs públicas por simplicidad y compatibilidad
Ver respuesta de ejemplo
Existen dos enfoques principales para versionar una API REST: versionado por URI y versionado por cabecera de solicitud personalizada. El versionado por URI (ej. /v1/usuarios) es directo: la versión está en la ruta, lo que facilita el enrutamiento, la depuración y permite que diferentes versiones coexistan con distintas URLs, favoreciendo la caché. Su desventaja es que contamina la URL y puede resultar en duplicación de código. El versionado por cabecera (ej. Accept: application/vnd.api.v1+json) mantiene las URLs limpias, pero es menos visible y requiere que los clientes configuren cabeceras específicas; además, dificulta el enrutamiento y la negociación de contenido. Personalmente, recomiendo el versionado por URI para APIs públicas, ya que es más explícito y reduce la fricción para los desarrolladores. Sin embargo, si se prioriza la estética de las URLs y se tiene control sobre los clientes, el versionado por cabecera es una alternativa válida. Ambos pueden combinarse con un enfoque de no versionar mientras sea posible, usando cambios compatibles hacia atrás.
- Escribe un middleware simple de Express.js que valide una clave API del encabezado Authorization y devuelva un 401 si falta o es inválida.Lo que cubre una buena respuesta
- Extraer API Key del header Authorization con formato Bearer
- Comparar con lista de claves válidas (idealmente usando Set para O(1))
- Devolver 401 con mensaje JSON descriptivo en caso de error
- Llamar a next() si la clave es válida
- Considerar seguridad adicional como limitación de intentos
Ver respuesta de ejemplo
El middleware de Express.js se encarga de verificar que el header Authorization contenga un Bearer token válido. Primero verificamos que el header exista y comience con 'Bearer ', de lo contrario respondemos con 401. Luego extraemos la clave y la comparamos contra un conjunto de claves autorizadas (idealmente un Set para búsqueda en O(1)). Si no coincide, devolvemos 401. Si es válida, llamamos a next() para continuar con la ejecución. Este middleware puede aplicarse a rutas específicas usando app.use. Para producción, se recomienda almacenar las claves en una base de datos o servicio externo, y considerar añadir cache y limitación de tasa.
Solución de referenciajavascript // Middleware de Express.js para validar API Key en el header Authorization const validApiKeys = ['mi-clave-secreta-123', 'otra-clave-valida']; // En producción, usar BD o variables de entorno const apiKeyMiddleware = (req, res, next) => { const authHeader = req.headers['authorization']; // Verificar que el header exista y tenga el formato 'Bearer <api_key>' if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Falta el header Authorization o formato inválido' }); } const apiKey = authHeader.split(' ')[1]; // Validar contra lista de claves permitidas if (!validApiKeys.includes(apiKey)) { return res.status(401).json({ error: 'API Key inválida' }); } // Si es válida, continuar next(); }; // Uso: app.use('/api', apiKeyMiddleware); // Proteger rutas bajo /api // Complejidad temporal: O(1) si se usa un Set para búsqueda. Espacial: O(n) donde n es número de claves. - Diseña una estructura de respuesta de error para una API de pagos. Incluye ejemplos para errores de validación, fondos insuficientes y errores de servidor.Lo que cubre una buena respuesta
- Estructura JSON consistente: { error: { code, message, details } }
- Códigos de error descriptivos para cada tipo de error
- HTTP status codes apropiados: 400 validación, 402 fondos insuficientes, 500 servidor
- Incluir detalles específicos para errores de validación (campo, razón)
- Proporcionar mensajes legibles por humanos y máquina
Ver respuesta de ejemplo
Para una API de pagos, diseño una estructura de error uniforme que incluya siempre un campo error con los subcampos code (string), message (string legible) y details (array opcional). Por ejemplo, para errores de validación: { error: { code: 'VALIDATION_ERROR', message: 'Datos de entrada inválidos', details: [ { field: 'amount', message: 'El monto debe ser positivo' } ] } }. Para fondos insuficientes: { error: { code: 'INSUFFICIENT_FUNDS', message: 'No hay suficiente saldo en la cuenta' } }. Para errores internos del servidor: { error: { code: 'INTERNAL_ERROR', message: 'Error inesperado. Intente más tarde' } }. Los códigos de estado HTTP acompañan: 400 para validación, 402 (o 403) para fondos, 500 para errores de servidor. La inclusión de un trace ID opcional ayuda a depurar. Esta estructura permite a los clientes manejar errores de forma programática y mostrar mensajes apropiados.
- ¿Cómo implementarías la limitación de tasa para una API pública? Describe el algoritmo y cómo comunicarías los límites a los clientes.Lo que cubre una buena respuesta
- Algoritmo de ventana deslizante o token bucket usando Redis
- Headers de respuesta: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset
- Respuesta 429 con Retry-After cuando se excede el límite
- Identificar clientes por API key, IP o token de autenticación
- Considerar fairness y burst allowance
Ver respuesta de ejemplo
Para implementar limitación de tasa (rate limiting) en una API pública, utilizo el algoritmo de ventana deslizante (sliding window) almacenando contadores en Redis. Cada solicitud incrementa un contador asociado a la clave del cliente (ej. API key o IP) con un tiempo de expiración. Antes de procesar la solicitud, se verifica si el contador ha excedido el límite (ej. 100 peticiones por hora). Si se excede, respondo con HTTP 429 Too Many Requests e incluyo el header Retry-After con el tiempo de espera en segundos. Además, comunico los límites a los clientes mediante los headers estándar: X-RateLimit-Limit (límite máximo), X-RateLimit-Remaining (peticiones restantes en la ventana actual) y X-RateLimit-Reset (timestamp de reinicio). La ventana deslizante evita picos repentinos y es más precisa que la ventana fija. Los trade-offs incluyen el uso de memoria en Redis y la complejidad de sincronización en sistemas distribuidos. Es importante documentar claramente estos headers para que los clientes puedan adaptar su comportamiento.
Cómo prepararse
- Siempre comienza identificando los recursos y sus relaciones, luego mapealos a endpoints con métodos HTTP adecuados.
- Simula una API simple en tu lenguaje preferido (por ejemplo, Node.js/Express, Python/Flask) para practicar operaciones CRUD y manejo de errores.
- Estudia patrones comunes de diseño de API como HATEOAS, idempotencia para PUT/DELETE y métodos seguros/idempotentes.
- Prepárate para discutir trade-offs: por ejemplo, por qué elegirías un enfoque de paginación o estrategia de versionado particular.
- Practica la pizarra: explica claramente tus decisiones de diseño para la nomenclatura de recursos, códigos de estado y formatos de error.
Preguntas frecuentes
¿Cuál es el error más común en el diseño de API REST?
Usar verbos en los endpoints (por ejemplo, /users/getUser en lugar de GET /users/:id). También, no usar códigos de estado HTTP adecuados para errores.
¿Debería usar siempre HATEOAS?
HATEOAS es una restricción para la adhesión completa a REST, pero para microservicios prácticos, a menudo se omite por simplicidad. Enfócate en enlaces de recursos claros.
¿Cómo manejo las actualizaciones parciales?
Usa PATCH con un formato JSON Patch o Merge Patch. Para actualizaciones parciales, incluye solo los campos a cambiar en el cuerpo de la solicitud.
¿Cuál es la mejor manera de versionar una API?
El versionado por URI (por ejemplo, /v1/users) es explícito y fácil de cachear, pero el versionado por cabecera mantiene las URL limpias. Elige según las preferencias de tu equipo.
¿Cómo pruebo una entrevista de diseño de API REST?
Practica con entrevistas simuladas en una pizarra o usando herramientas como Postman. Enfócate en explicar los trade-offs y tu proceso de pensamiento.
Practica preguntas sobre REST API design con retroalimentación instantánea de IA
Sube tu currículum, obtén una entrevista simulada personalizada y ve exactamente qué mejorar — gratis para empezar.