Objetivo: consultas expresivas sin perder consistencia
En colecciones REST, el filtrado y el ordenamiento suelen crecer “orgánicamente” hasta volverse inconsistentes: cada endpoint inventa sus propios parámetros, aparecen combinaciones ambiguas y el backend termina aceptando cualquier cosa. La meta es definir un esquema único y predecible para: filtrar (qué registros), ordenar (en qué orden), seleccionar campos (qué atributos devolver) y expandir relaciones (qué datos relacionados incluir), manteniendo reglas claras de validación y límites para evitar respuestas excesivas.
Esquema recomendado de parámetros de consulta
Propón un conjunto pequeño y consistente de parámetros, reutilizable en todas las colecciones:
status=active(filtros simples por igualdad)createdAt[gte]=2025-01-01T00:00:00Z(operadores explícitos)q=texto(búsqueda libre controlada)sort=field,-otherField(ordenamiento)fields=id,name,status(selección de campos)expand=customer,items.product(expansión controlada)
Regla de oro: un mismo significado, un mismo parámetro. Evita variantes como orderBy en un endpoint y sortBy en otro, o search vs q.
Filtrado: operadores comunes y sintaxis
1) Igualdad simple (forma corta)
Para el caso más frecuente, permite field=value como atajo de igualdad:
GET /orders?status=activeEsto equivale a status[eq]=active. Mantener el atajo mejora la ergonomía sin perder formalidad.
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
2) Operadores explícitos (forma extendida)
Para comparaciones y casos no triviales, usa la forma field[op]=value. Operadores recomendados:
| Operador | Significado | Ejemplo |
|---|---|---|
eq | igual | status[eq]=active |
neq | distinto | status[neq]=archived |
lt | menor que | total[lt]=100 |
lte | menor o igual | total[lte]=100 |
gt | mayor que | total[gt]=100 |
gte | mayor o igual | createdAt[gte]=2025-01-01T00:00:00Z |
in | pertenece a un conjunto | status[in]=active,pending |
contains | contiene (texto/colección) | name[contains]=pro |
Recomendación: documenta si contains es sensible a mayúsculas, si usa normalización (acentos) y si aplica a texto, arrays o ambos.
3) Búsqueda libre con q (controlada)
q es útil para una búsqueda “tipo caja de búsqueda” sin exponer la complejidad del motor (SQL full-text, Elastic, etc.). Mantén q como un contrato funcional, no como un lenguaje.
GET /customers?q=juan perezBuenas prácticas para q:
- Define qué campos participa (p. ej.,
name,email) y si hay ponderación. - Limita longitud (p. ej., 200 caracteres) y caracteres permitidos.
- No aceptes sintaxis avanzada (AND/OR, wildcards) salvo que la estandarices y valides estrictamente.
Reglas de validación (para evitar “filtros sorpresa”)
Un esquema consistente requiere validación explícita. Define una lista blanca por recurso:
- Campos filtrables: qué atributos aceptan filtros.
- Operadores permitidos por campo: no todos los operadores aplican a todos los tipos.
- Tipos y formatos: fechas ISO-8601, números, booleanos, enums.
- Límites: tamaño máximo de listas en
in, longitud de cadenas, cantidad total de filtros.
Matriz de ejemplo: campos vs operadores
| Campo | Tipo | Operadores permitidos | Notas |
|---|---|---|---|
status | enum | eq, neq, in | Validar contra valores conocidos |
createdAt | datetime | eq, lt, lte, gt, gte | ISO-8601 con zona horaria |
total | number | eq, lt, lte, gt, gte | Rangos coherentes |
name | string | eq, contains | Definir sensibilidad a mayúsculas |
Errores típicos y cómo responder
Cuando el cliente envía filtros inválidos, responde con un error de validación (p. ej., 400) indicando exactamente qué parámetro falló y por qué. Ejemplos de causas:
- Campo no filtrable:
GET /orders?internalNote[contains]=x - Operador no permitido:
GET /orders?status[gte]=active - Formato inválido:
createdAt[gte]=ayer - Lista
indemasiado grande:status[in]=...con cientos de valores
Recomendación práctica: incluye en el error un código interno (p. ej., INVALID_FILTER) y una lista de detalles por parámetro para que el cliente pueda corregir sin ensayo y error.
Guía práctica paso a paso para diseñar filtros consistentes
Paso 1: define el “contrato de filtrado” por recurso
Para cada colección, crea una tabla interna (o en tu especificación) con:
- Campos filtrables
- Operadores por campo
- Tipo y formato
- Ejemplos válidos
- Restricciones (máximos, mínimos, longitudes)
Paso 2: estandariza el parseo de parámetros
Implementa un parser único que convierta la query en una estructura normalizada. Ejemplo conceptual:
// Query: ?status=active&createdAt[gte]=2025-01-01T00:00:00Z&total[lt]=100&status[in]=active,pendingfilters = [ { field: "status", op: "eq", value: "active" }, { field: "createdAt", op: "gte", value: "2025-01-01T00:00:00Z" }, { field: "total", op: "lt", value: 100 }, { field: "status", op: "in", value: ["active", "pending"] }]Decide una regla para conflictos: por ejemplo, si llega status=active y status[in]=active,pending, ¿se combinan con AND, se rechaza o gana uno? Lo más seguro es rechazar ambigüedad con un error claro.
Paso 3: valida antes de ejecutar
Valida: campo permitido, operador permitido, tipo, formato, límites. No delegues esto al motor de base de datos.
Paso 4: traduce a la capa de persistencia
Traduce la estructura normalizada a SQL/ORM/Elastic con mapeos explícitos. Evita concatenar strings de query para prevenir inyecciones y comportamientos inesperados.
Ordenamiento: sort=field,-otherField
Define un parámetro único sort con una lista separada por comas. Un prefijo - indica descendente; sin prefijo, ascendente.
GET /orders?sort=createdAt,-totalInterpretación:
createdAtascendentetotaldescendente
Lista blanca de campos ordenables
No permitas ordenar por cualquier campo. Mantén un conjunto de campos ordenables por recurso (y, si aplica, por rol). Si el cliente envía un campo no permitido:
- Opción recomendada: rechazar con error de validación (evita resultados “silenciosamente distintos”).
- Alternativa: ignorar campos inválidos, pero documenta y devuelve advertencias (menos ideal).
Reglas adicionales
- Define un orden por defecto estable (p. ej.,
sort=-createdAt), para evitar resultados que “saltan” entre llamadas. - Limita la cantidad de campos en
sort(p. ej., máximo 3) para proteger rendimiento. - Si ordenas por campos nullable, documenta dónde caen los nulls (primero/último) o estandariza el comportamiento.
Selección de campos: fields=... para payloads más pequeños
fields permite que el cliente pida solo ciertos atributos del recurso, reduciendo ancho de banda y costo de serialización.
GET /customers?fields=id,name,statusReglas recomendadas
- Lista blanca de campos seleccionables (por seguridad y consistencia).
- Define si
fieldses una lista inclusiva (lo usual) o exclusiva (evítalo). - Define campos mínimos obligatorios (p. ej.,
id) aunque no se pidan, si tu contrato lo requiere. - Limita la cantidad de campos solicitables (p. ej., máximo 30) para evitar respuestas enormes por abuso.
Ejemplo con respuesta más ligera
GET /orders?fields=id,status,totalDevuelve solo esos atributos (y omite el resto), manteniendo el mismo envelope/estructura de respuesta que uses en tu API.
Expansión controlada: expand=... para evitar múltiples llamadas
expand permite incluir recursos relacionados de forma controlada, evitando que el cliente haga muchas solicitudes para construir una vista. La clave es que sea opt-in y con límites.
GET /orders?expand=customer,items.productEsto podría incluir:
customerembebido dentro de cada order- en
items, cada item con suproduct
Reglas para que expand no se vuelva incontrolable
- Lista blanca de expansiones permitidas por recurso (y por rol).
- Profundidad máxima (p. ej., 2 niveles:
items.productsí,items.product.supplierno). - Máximo de expansiones por request (p. ej., 3).
- Campos por defecto en recursos expandidos (idealmente un “resumen”), o permitir
fieldstambién para expandidos mediante una convención documentada. - Coste controlado: si una expansión dispara joins pesados, considera rechazarla o degradarla (p. ej., solo IDs) según políticas.
Evitar respuestas excesivas (límites combinados)
La combinación de expand + fields + filtros puede generar respuestas muy grandes. Define límites globales:
- Máximo de expansiones y profundidad (ya mencionado).
- Máximo de tamaño de respuesta (si tu infraestructura lo permite) o límites de entidades embebidas (p. ej., máximo 50 items expandidos por order).
- Si el recurso tiene subcolecciones grandes, no las expandas completas: expande solo un resumen o un subconjunto predecible.
Ejemplos completos (combinando todo)
Ejemplo 1: filtrar por estado y rango de fechas, ordenar y seleccionar campos
GET /orders?status[in]=active,pending&createdAt[gte]=2025-01-01T00:00:00Z&createdAt[lt]=2025-02-01T00:00:00Z&sort=-createdAt&fields=id,status,createdAt,totalEjemplo 2: búsqueda libre con q y expansión limitada
GET /customers?q=ana garcia&sort=name&fields=id,name,email&expand=lastOrderAsegúrate de que lastOrder sea una expansión explícitamente soportada (no “cualquier relación”).
Ejemplo 3: validación de campo no permitido en sort
GET /orders?sort=-internalMarginSi internalMargin no está en la lista blanca de ordenamiento, devuelve un error de validación indicando el campo inválido y, si es útil, los campos permitidos.
Checklist de implementación (rápida y accionable)
- Define por recurso: filterableFields, sortableFields, selectableFields, expandablePaths.
- Implementa un parser único para
field[op]=valuey atajofield=value. - Valida: campo, operador, tipo, formato, límites (listas, longitudes, cantidad de filtros).
- Estándar de ordenamiento:
sort=field,-othercon lista blanca y máximo de campos. - Estándar de selección:
fields=...con lista blanca, máximos y campos mínimos. - Estándar de expansión:
expand=...con lista blanca, profundidad y máximos. - Documenta ejemplos por endpoint y casos de error típicos para acelerar adopción por clientes.