Capa de servicios y conexión a la base de datos
Capa de servicios con Fetch tipado, introducción a Prisma ORM 7 y primera conexión real a PostgreSQL.
Objetivos de aprendizaje
- Consolidar el patrón fetchJSON genérico como capa de servicios del frontend
- Comprender por qué los componentes nunca llaman a fetch directamente
- Instalar y configurar Prisma ORM 7 con @prisma/adapter-pg
- Definir el schema.prisma con los modelos Articulo y Categoria
- Reemplazar el array mock del backend por consultas reales a PostgreSQL con Prisma
🎯 Objetivo de la semana: Al terminar habrás consolidado
articulosService.tscomo la capa de servicios tipada del frontend y conectado el backend a PostgreSQL real usando Prisma ORM 7 — sin cambiar una sola línea del código React.🔑 Concepto clave: Separación cliente-servidor — el frontend no sabe si los datos vienen de un array en memoria, de PostgreSQL o de una API externa. Solo ve el contrato de
articulosService. Si el backend cambia, solo cambia el backend.🛠 Tarea práctica: Esta semana tiene dos mitades:
- Frontend: revisar y consolidar
articulosService.tsde S6 (manejo de errores correcto, tipos genéricos,resolverImagen)- Backend: instalar Prisma 7, definir
schema.prisma, ejecutar la primera migración y reemplazar el array mock porprisma.articulo.findMany()📋 Entregable:
GET /api/v1/articulosdevuelve artículos reales de PostgreSQL. LaHomePagelos muestra sin cambiar una sola línea del código React. TanStack Query y Axios no se usan en este proyecto.
1. La capa de servicios: por qué nunca fetch directo
En la Semana 6 creaste articulosService.ts. Revisemos por qué el patrón importa y qué lo hace correcto.
La regla es simple: ningún componente llama a fetch directamente. Si HomePage hace fetch('/api/v1/articulos') y la URL cambia a /api/v2/articulos, tienes que encontrar y cambiar cada componente que haga esa llamada. Con la capa de servicios, el cambio es en un solo lugar.
El fetchJSON<T> genérico es el corazón del servicio. Hay dos detalles críticos que el código debe tener:
// src/services/articulosService.ts — patrón correcto completo
async function fetchJSON<T>(url: string, init?: RequestInit, noCache = false): Promise<T> {
const res = await fetch(`${BASE}${url}`, {
headers: { 'Content-Type': 'application/json' },
cache: noCache ? 'no-store' : 'default',
...init,
})
// ⚠️ fetch NO lanza error en 4xx/5xx — hay que verificar res.ok manualmente
if (!res.ok) {
const body = await res.json().catch(() => ({}))
// Usa el mensaje del servidor si existe, no solo el código HTTP
throw new Error(body.message ?? `HTTP ${res.status}`)
}
return res.json() as Promise<T>
}El flag noCache es importante para el panel admin: el backend usa Cache-Control: max-age=60 en los GET, lo que hace que el navegador sirva respuestas cacheadas en lugar de pedir datos frescos. Al llamar con noCache: true, el servicio fuerza una petición real al servidor:
// Panel admin — siempre datos frescos
const res = await articulosService.getAll(undefined, true) // noCache = true
// Página pública — puede usar caché
const res = await articulosService.getAll({ estado: 'publicado' })2. Prisma ORM 7: introducción
Prisma es un ORM (Object-Relational Mapper) que genera un cliente TypeScript fuertemente tipado a partir de tu schema. En lugar de escribir SQL manualmente, escribes consultas como objetos TypeScript y Prisma genera el SQL correcto para tu base de datos.
Prisma 7 tiene cambios breaking respecto a versiones anteriores:
| Aspecto | Prisma < 7 | Prisma 7 |
|---|---|---|
DATABASE_URL | En schema.prisma | En prisma.config.ts |
| Driver | Interno | @prisma/adapter-pg explícito |
| Cliente generado | node_modules/.prisma/ | generated/prisma/ (configurable) |
| Generator | prisma-client-js | prisma-client |
La instalación en el backend:
# Desde backend/
pnpm add prisma @prisma/client @prisma/adapter-pg
pnpm approve-builds # autoriza la compilación de binarios nativos de Prisma3. Configurar Prisma 7 en el backend
prisma.config.ts
En Prisma 7 la DATABASE_URL ya no va en schema.prisma — va en un archivo de configuración dedicado:
// backend/prisma.config.ts
import path from 'node:path'
import { defineConfig } from 'prisma/config'
export default defineConfig({
earlyAccess: true,
schema: path.join(import.meta.dirname, 'prisma/schema.prisma'),
})La DATABASE_URL se lee del .env directamente a través del adapter PrismaPg.
schema.prisma
// backend/prisma/schema.prisma
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
model Categoria {
id Int @id @default(autoincrement())
nombre String
slug String @unique
color String
articulos Articulo[]
}
model Articulo {
id Int @id @default(autoincrement())
titulo String
slug String @unique
extracto String?
contenido String?
imagen String?
categoriaId Int
categoria Categoria @relation(fields: [categoriaId], references: [id])
estado EstadoArticulo @default(borrador)
autor String @default("Administrador")
tiempoLectura Int @default(5)
fechaPublicacion DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum EstadoArticulo {
borrador
publicado
archivado
}db.ts — cliente con adapter
// backend/src/config/db.ts
import { PrismaPg } from '@prisma/adapter-pg'
import { PrismaClient } from '../generated/prisma/client'
import 'dotenv/config'
const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL ?? ''
})
export const prisma = new PrismaClient({ adapter })
prisma.$connect()
.then(() => console.log('✅ PostgreSQL conectado via Prisma'))
.catch((e) => console.error('❌ Error Prisma:', e))4. Primera migración y reemplazar el mock
Ejecutar la migración
# Desde backend/
npx prisma migrate dev --name init
npx prisma generate # regenerar el cliente TypeScriptEsto crea las tablas en PostgreSQL y genera el cliente tipado en src/generated/prisma/.
Seed de datos iniciales
# backend/package.json
{
"scripts": {
"seed": "npx tsx prisma/seed.ts"
}
}// backend/prisma/seed.ts
import { prisma } from '../src/config/db'
async function main() {
// Crear categorías
const tech = await prisma.categoria.upsert({
where: { slug: 'tecnologia' },
update: {},
create: { nombre: 'Tecnología', slug: 'tecnologia', color: '#3b82f6' },
})
// Crear artículo de prueba
await prisma.articulo.upsert({
where: { slug: 'primer-articulo' },
update: {},
create: {
titulo: 'Mi primer artículo',
slug: 'primer-articulo',
extracto: 'Este es el extracto del artículo.',
contenido: '<p>Contenido del artículo.</p>',
categoriaId: tech.id,
estado: 'publicado',
},
})
console.log('✅ Seed completado')
}
main().catch(console.error).finally(() => prisma.$disconnect())Reemplazar el mock en el router
// backend/src/routes/articulos.ts — conectado a Prisma
import { Router } from 'express'
import { prisma } from '../config/db'
const router = Router()
router.get('/', async (req, res) => {
const { estado, busqueda } = req.query
const where: Record<string, unknown> = {}
if (estado && estado !== 'todos') where.estado = String(estado)
if (busqueda) where.titulo = { contains: String(busqueda), mode: 'insensitive' }
const articulos = await prisma.articulo.findMany({
where,
include: { categoria: true },
orderBy: { createdAt: 'desc' },
})
res.json({ status: 'success', data: articulos })
})
router.get('/:slug', async (req, res) => {
const articulo = await prisma.articulo.findUnique({
where: { slug: req.params.slug },
include: { categoria: true },
})
if (!articulo) return res.status(404).json({ status: 'error', message: 'No encontrado' })
res.json({ status: 'success', data: articulo })
})
export default routerResultado esperado:
GET /api/v1/articulosdevuelve artículos de PostgreSQL. LaHomePageyDetallePagelos muestran sin cambiar una sola línea del código React — el contrato dearticulosServiceno cambió.
Actividades prácticas
Actividad 1 — Revisar articulosService (30 min)
Verifica que tu articulosService.ts verifica res.ok, lanza Error con el mensaje del servidor y tiene resolverImagen. Llama a articulosService.getAll({ estado: 'publicado' }) desde la HomePage y confirma que los artículos se cargan desde el servidor Express.
Actividad 2 — Configurar Prisma 7 (60 min)
Instalar Prisma, crear schema.prisma con los modelos exactos del proyecto, ejecutar migrate dev --name init, verificar que las tablas existen en PostgreSQL con npx prisma studio.
Actividad 3 — Seed y primer GET con Prisma (45 min)
Crear prisma/seed.ts con al menos 2 categorías y 4 artículos. Ejecutar pnpm seed. Reemplazar el array mock en el router por prisma.articulo.findMany(). Verificar que GET /api/v1/articulos devuelve los datos del seed.
Actividad 4 — Verificar end-to-end (30 min)
Con el backend conectado a PostgreSQL y el frontend corriendo, confirma que la HomePage muestra los artículos del seed. Añade un artículo directamente en Prisma Studio y verifica que aparece en el frontend al recargar.
🛠 Proyecto CMS — Semana 7: Backend conectado a PostgreSQL
Al terminar esta semana:
- El backend usa Prisma 7 para leer artículos de PostgreSQL
- El frontend usa
articulosService(no TanStack Query, no Axios) para consumir la API npm run devdesde la raíz levanta ambos procesos — frontend en 5173, backend en 3001npx prisma studiodesdebackend/permite ver y editar los datos directamente
📁 Archivos de esta semana
backend/
├── prisma.config.ts ← NUEVO: configuración Prisma 7 (apunta al schema)
├── prisma/ ← carpeta NUEVA esta semana
│ ├── schema.prisma ← NUEVO: modelos Articulo y Categoria
│ ├── seed.ts ← NUEVO: datos iniciales de prueba
│ └── migrations/ ← GENERADO automáticamente por `npx prisma migrate dev`
│ └── 20xxxxxx_init/
└── src/
├── config/ ← carpeta NUEVA esta semana
│ └── db.ts ← NUEVO: cliente Prisma con PrismaPg adapter
├── generated/ ← GENERADO por `npx prisma generate`
│ └── prisma/ ← cliente TypeScript tipado, NO editar a mano
└── routes/
└── articulos.ts ← ACTUALIZADO: mock → prisma.articulo.findMany()Prisma 7 — dónde van los archivos:
DATABASE_URL→ enbackend/.env, NO enschema.prismaprisma.config.ts→ en la raíz debackend/, no dentro desrc/- Cliente generado → en
src/generated/prisma/(no ennode_modules/)- Import del cliente →
import { PrismaClient } from '../generated/prisma/client'
En la Semana 8 añadirás los endpoints de escritura (POST, PUT, DELETE) y la arquitectura MVC completa con controllers, services y la capa de queries Prisma.
📥 Descarga el entregable de esta semana
¿No lograste completar la semana o quieres comparar tu código con la solución? El entregable de la Semana 7 está incluido junto con el de la Semana 8 (se implementaron en el mismo sprint):
⬇️ Descargar entregable Semanas 7-8 (.zip)
Qué incluye: backend completo con Prisma 7 (prisma.config.ts, schema.prisma, src/config/db.ts, src/generated/prisma/), primera migración y seed de datos, rutas GET conectadas a PostgreSQL y la arquitectura MVC completa con todos los endpoints CRUD.
Setup después de descomprimir:
cd blog-cms
npm install
cd backend && pnpm install
# Crea backend/.env con: DATABASE_URL="postgresql://..."
npx prisma migrate dev
pnpm seed
cd ..
npm run dev