Lección 15 de 17 · Lectura y práctica
Desarrollar Endpoints con Queries Optimizadas
Tipo de lección: VideoConcepto claveLas queries optimizadas en Prisma son consultas a la base de datos que maximizan el rendimiento y minimizan el consumo de recursos. Piensa en ellas como la diferencia entre pedir un café en un.
Del concepto a la práctica
Taller de esta lección
Un endpoint debe validar parámetros antes de consultar y construir una respuesta limitada al contrato elegido. Para GET /rooms/:id/reservations, valida el identificador, filtra por sala y devuelve fecha, estado y nombre del usuario, no el modelo completo.
Ejemplo resuelto
async function listarReservas(req,res) {
const roomId = Number(req.params.id);
if (!Number.isInteger(roomId) || roomId <= 0) return res.status(400).json({ error: "roomId inválido" });
const data = await db.reservation.findMany({
where: { roomId, status: "CONFIRMED" },
select: { startsAt: true, endsAt: true, user: { select: { name: true } } },
orderBy: [{ startsAt: "asc" }, { id: "asc" }], take: 20
});
return res.json(data);
}Valida cadena de dígitos y Number.isSafeInteger(roomId)>0; Number admite otras representaciones que quizá no quieras aceptar. Antes de consultar comprueba identidad y permiso sobre la sala, ya que parametrizar no autoriza datos. Este endpoint no muestra status pese a la descripción: añádelo al select si lo necesita el contrato. Fixture pararoom2: reservas10/11/12 CONFIRMED a10/11/12h y13 PENDING a13h el2026-01-02, cada una dura1h con usuario existente; salida3 confirmadas. Una sala válida sin reservas devuelve[]; una sala inexistente requiere la política404 explícita.
Pruébalo
Valida ruta y parámetros de página.
Define filtro y campos de salida.
Añade orden y límite.
Prueba entrada inválida y válida.
Comprueba tu resultado
/rooms/2/reservations devuelve como máximo 20 reservas confirmadas; /rooms/x/reservations responde 400 sin consulta.
Error frecuente: interpolar texto sin validar. Corrígelo convirtiendo y comprobando el entero.
Ejercicio: con 3 confirmadas y 1 pendiente para la sala 2, el resultado esperado contiene 3 filas.

Mapa de prácticaUna clave conecta cada pedido con su cliente. INNER JOIN devuelve las coincidencias y puede producir varias filas por cliente. LEFT JOIN conserva también los clientes sin pedido y representa los campos ausentes con NULL.
Ver la infografía en tamaño completo ↗Tipo de lección: Video
Concepto clave
Las queries optimizadas en Prisma son consultas a la base de datos que maximizan el rendimiento y minimizan el consumo de recursos. Piensa en ellas como la diferencia entre pedir un café en un bar: una query no optimizada es como pedir "un café" y esperar que el barista adivine todo (tipo, tamaño, extras), mientras que una optimizada es dar instrucciones precisas como "café americano mediano, sin azúcar". En producción, cada milisegundo cuenta y cada conexión a la base de datos tiene un costo.
La optimización se enfoca en tres pilares: selectividad (traer solo los datos necesarios), relaciones eficientes (evitar el problema N+1) y uso de índices (acelerar búsquedas). Una query mal diseñada puede funcionar en desarrollo con 100 registros, pero colapsar en producción con millones. La clave es pensar como un arquitecto de bases de datos, no solo como un desarrollador de API.
Cómo funciona en la práctica
Imagina que desarrollas una API para un e-commerce. Necesitas un endpoint que devuelva los últimos 10 pedidos con sus productos y el cliente. El enfoque naive seria: 1) Obtener los pedidos, 2) Para cada pedido, hacer una query para sus productos, 3) Para cada pedido, otra query para el cliente. Esto genera 1 + 10 + 10 = 21 queries (problema N+1).
El enfoque optimizado usa include con selectividad y paginacion:
- Definir exactamente que campos necesitas de cada modelo
- Usar
includepara traer relaciones en una sola query - Aplicar
takeyskippara paginacion - Ordenar por un campo indexado como
createdAt
Esto reduce las 21 queries a solo 1, con un impacto dramatico en el rendimiento.
Codigo en accion
Primero, veamos el antes (query no optimizada):
// ENDPOINT NO OPTIMIZADO - PROBLEMA N+1
app.get('/pedidos/lentos', async (req, res) => {
const pedidos = await prisma.pedido.findMany({
take: 10,
orderBy: { createdAt: 'desc' }
});
// PROBLEMA: queries adicionales por cada pedido
const pedidosConDetalles = await Promise.all(
pedidos.map(async (pedido) => {
const productos = await prisma.producto.findMany({
where: { pedidoId: pedido.id }
});
const cliente = await prisma.cliente.findUnique({
where: { id: pedido.clienteId }
});
return { ...pedido, productos, cliente };
})
);
res.json(pedidosConDetalles);
});Ahora el despues (query optimizada):
// ENDPOINT OPTIMIZADO - 1 QUERY
app.get('/pedidos/optimizados', async (req, res) => {
const pedidos = await prisma.pedido.findMany({
take: 10,
skip: 0, // Para paginacion
orderBy: { createdAt: 'desc' }, // Campo indexado
select: {
id: true,
total: true,
createdAt: true,
// Solo campos necesarios
cliente: {
select: {
id: true,
nombre: true,
email: true // No traer toda la info del cliente
}
},
productos: {
select: {
id: true,
nombre: true,
precio: true,
cantidad: true
},
where: { activo: true } // Filtro adicional
}
},
where: {
estado: 'COMPLETADO' // Filtro principal
}
});
res.json(pedidos);
});Errores comunes
- Traer todos los campos: Usar
select: trueen lugar deselectespecifico. Solucion: Listar explicitamente los campos necesarios. - Olvidar los índices: Ordenar o filtrar por campos no indexados. Solucion: Verificar índices en el schema y agregarlos donde sea necesario.
- N+1 en relaciones anidadas: Incluir relaciones pero no sus sub-relaciones. Solucion: Usar
includeanidado con selectividad. - Paginacion incompleta: Usar solo
takesinskippara paginas siguientes. Solucion: Implementar logica completa de paginacion. - Filtros ineficientes: Usar
ORcon muchos condiciones oLIKEsin limites. Solucion: Revisar planes de ejecucion y simplificar condiciones.
Checklist de dominio
- ¿Uso
selecten lugar deincludecuando solo necesito campos, no relaciones completas? - ¿Verifico que los campos en
orderByywhereprincipales tengan índices? - ¿Evito el problema N+1 usando
includepara traer relaciones en una sola query? - ¿Implemento paginacion con
takeyskippara limites de datos? - ¿Uso
wherepara filtrar antes de traer datos, no después en JavaScript? - ¿Reviso el plan de ejecucion de queries complejas con
prisma.$queryRawyEXPLAIN? - ¿Considero usar transacciones para operaciones multiples que deben ser atomicas?
Optimizar endpoint de usuarios con pedidos recientes
En este ejercicio, optimizaras un endpoint existente que tiene problemas de rendimiento. Trabajaras con un schema de Prisma para una aplicacion de pedidos.
- Contexto: Tienes un endpoint
GET /usuarios/:id/pedidos-recientesque devuelve los ultimos 5 pedidos de un usuario, con los productos de cada pedido. Actualmente hace 1 + 5 + (5 * N) queries (donde N es productos por pedido). - Schema relevante:
model Usuario {
id Int @id @default(autoincrement())
email String @unique
nombre String
pedidos Pedido[]
}
model Pedido {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
usuarioId Int
usuario Usuario @relation(fields: [usuarioId], references: [id])
productos ProductoPedido[]
}
model ProductoPedido {
id Int @id @default(autoincrement())
pedidoId Int
productoId Int
cantidad Int
pedido Pedido @relation(fields: [pedidoId], references: [id])
producto Producto @relation(fields: [productoId], references: [id])
}
model Producto {
id Int @id @default(autoincrement())
nombre String
precio Float
productoPedidos ProductoPedido[]
}- Codigo actual no optimizado:
app.get('/usuarios/:id/pedidos-recientes', async (req, res) => {
const usuarioId = parseInt(req.params.id);
// 1. Obtener usuario
const usuario = await prisma.usuario.findUnique({
where: { id: usuarioId }
});
// 2. Obtener ultimos 5 pedidos
const pedidos = await prisma.pedido.findMany({
where: { usuarioId: usuarioId },
take: 5,
orderBy: { createdAt: 'desc' }
});
// 3. Para cada pedido, obtener productos (PROBLEMA N+1)
const pedidosConProductos = await Promise.all(
pedidos.map(async (pedido) => {
const productosPedido = await prisma.productoPedido.findMany({
where: { pedidoId: pedido.id },
include: { producto: true }
});
return {
...pedido,
productos: productosPedido.map(pp => ({
...pp.producto,
cantidad: pp.cantidad
}))
};
})
);
res.json({
usuario: { id: usuario.id, nombre: usuario.nombre },
pedidos: pedidosConProductos
});
});- Tu tarea: Refactoriza este endpoint para que use maximo 2 queries en total. Aplica:
- Selectividad: Trae solo los campos necesarios
- Include anidado: Para relaciones usuario-pedidos-productos
- Paginacion: Manten
take: 5 - Filtros: Asegurate de que
createdAteste indexado
- Entrega: Proporciona el codigo completo del endpoint optimizado. Incluye comentarios explicando cada decision de optimizacion.
Pistas
- Recuerda que puedes usar include anidado: usuario → pedidos → productosPedido → producto
- Considera usar select dentro de include para limitar los campos de producto
- Piensa si realmente necesitas todos los campos de ProductoPedido o solo algunos
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.
Sigue con Prisma ORM Profesional: Schemas, Migraciones y Queries Optimizadas para Producción
Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.
Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.