- Haz un fork de este repositorio
- Clona este repositorio
- Al finalizar, ejecuta los siguientes comandos:
git add .
git commit -m "done"
git push origin [master/main]
- Crea un Pull Request y envía tu entrega.
Crear un servidor Express desde cero que gestione una colección de películas en memoria. Al terminar este lab tendrás un servidor HTTP funcional con varias rutas y comprenderás cómo funcionan req, res, los métodos HTTP y los códigos de estado.
- Node.js v18+ instalado (
node --version) - npm instalado (
npm --version) - Postman o Thunder Client (extensión de VS Code)
- Haber leído el material del D1
Una API que permite:
- Listar todas las películas
- Buscar una película por ID
- Buscar películas por género
- Añadir una nueva película
- Calcular la nota media de todas las películas
Los datos vivirán en memoria (un array de JavaScript), sin base de datos todavía.
mkdir api-peliculas
cd api-peliculas
npm init -yVerifica que se creó package.json. Instala las dependencias:
npm install express dotenv
npm install --save-dev nodemonAbre package.json y añade los scripts:
{
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
}Comprobación: cat package.json debe mostrar express en dependencies y nodemon en devDependencies.
Crea .env en la raíz del proyecto:
PORT=3000
Crea .gitignore:
node_modules/
.env
Crea el archivo index.js con estos datos de ejemplo. No copies los datos tal cual — añade al menos 2 películas más de tu elección:
require('dotenv').config()
const express = require('express')
const app = express()
const PORT = process.env.PORT || 3000
// Middleware para parsear JSON
app.use(express.json())
// =====================
// DATOS EN MEMORIA
// =====================
let peliculas = [
{
id: 1,
titulo: 'Inception',
director: 'Christopher Nolan',
anio: 2010,
genero: 'ciencia-ficcion',
nota: 8.8
},
{
id: 2,
titulo: 'Pulp Fiction',
director: 'Quentin Tarantino',
anio: 1994,
genero: 'crimen',
nota: 8.9
},
{
id: 3,
titulo: 'El Señor de los Anillos',
director: 'Peter Jackson',
anio: 2001,
genero: 'fantasia',
nota: 8.8
}
// Añade aquí 2 películas más de tu elección
]
let nextId = 4 // Contador para asignar IDs únicos
// =====================
// RUTAS (las añadirás abajo)
// =====================
// =====================
// INICIAR SERVIDOR
// =====================
app.listen(PORT, () => {
console.log(`Servidor corriendo en http://localhost:${PORT}`)
})Añade esta ruta antes de app.listen:
// GET /peliculas → devuelve todas las películas
app.get('/peliculas', (req, res) => {
res.json(peliculas)
})Prueba: Arranca el servidor con npm run dev. En Postman o el navegador visita http://localhost:3000/peliculas. Debes ver el array completo.
// GET /peliculas/:id → devuelve una película por ID
app.get('/peliculas/:id', (req, res) => {
const id = Number(req.params.id)
const pelicula = peliculas.find(p => p.id === id)
if (!pelicula) {
return res.status(404).json({ error: 'Película no encontrada' })
}
res.json(pelicula)
})Prueba:
GET /peliculas/1→ debe devolver Inception con status 200GET /peliculas/99→ debe devolver{ "error": "Película no encontrada" }con status 404
Importante: Fíjate en el
Number(req.params.id). Los parámetros de ruta siempre llegan como string. Si no conviertes, la comparaciónp.id === idfallará porque compara número con string.
Modifica la ruta que ya tienes para soportar un query string opcional:
// GET /peliculas?genero=crimen → filtra por género
app.get('/peliculas', (req, res) => {
const { genero } = req.query
if (genero) {
const filtradas = peliculas.filter(p => p.genero === genero)
return res.json(filtradas)
}
res.json(peliculas)
})Prueba:
GET /peliculas→ todas las películasGET /peliculas?genero=ciencia-ficcion→ solo las de ese géneroGET /peliculas?genero=terror→ array vacío[](sin error, eso es correcto)
// POST /peliculas → crea una nueva película
app.post('/peliculas', (req, res) => {
const { titulo, director, anio, genero, nota } = req.body
// Validación: campos obligatorios
if (!titulo || !director || !anio || !genero) {
return res.status(400).json({
error: 'Los campos titulo, director, anio y genero son obligatorios'
})
}
// Validación: nota debe ser entre 0 y 10
if (nota !== undefined && (nota < 0 || nota > 10)) {
return res.status(400).json({
error: 'La nota debe estar entre 0 y 10'
})
}
const nuevaPelicula = {
id: nextId++,
titulo,
director,
anio: Number(anio),
genero,
nota: nota !== undefined ? Number(nota) : null
}
peliculas.push(nuevaPelicula)
// Status 201 = Created
res.status(201).json(nuevaPelicula)
})Prueba en Postman:
Request:
POST http://localhost:3000/peliculas
Content-Type: application/json
{
"titulo": "Interstellar",
"director": "Christopher Nolan",
"anio": 2014,
"genero": "ciencia-ficcion",
"nota": 8.6
}
Debe devolver la película con un nuevo ID y status 201.
Prueba también el caso de error:
{
"titulo": "Película sin director"
}Debe devolver status 400 con el mensaje de error.
// GET /estadisticas → nota media de todas las películas
app.get('/estadisticas', (req, res) => {
const conNota = peliculas.filter(p => p.nota !== null)
if (conNota.length === 0) {
return res.json({ media: null, total: 0 })
}
const suma = conNota.reduce((acc, p) => acc + p.nota, 0)
const media = (suma / conNota.length).toFixed(2)
res.json({
media: Number(media),
total: peliculas.length,
conNota: conNota.length
})
})Atención al orden de las rutas: Esta ruta debe declararse antes de
/peliculas/:id. ¿Por qué? Porque si está después, Express interpretaráestadisticascomo el:idy buscará una película con ese ID. Compruébalo cambiando el orden y observa qué pasa.
Prueba: GET /estadisticas → algo como { "media": 8.83, "total": 5, "conNota": 5 }
// DELETE /peliculas/:id → elimina una película
app.delete('/peliculas/:id', (req, res) => {
const id = Number(req.params.id)
const index = peliculas.findIndex(p => p.id === id)
if (index === -1) {
return res.status(404).json({ error: 'Película no encontrada' })
}
const eliminada = peliculas.splice(index, 1)[0]
res.json({ mensaje: 'Película eliminada', pelicula: eliminada })
})Prueba:
DELETE /peliculas/1→ elimina Inception, devuelve la película eliminadaGET /peliculas→ ya no aparece InceptionDELETE /peliculas/1otra vez → 404
Añade esto justo antes de app.listen:
// Esta ruta atrapa cualquier petición que no coincida con las anteriores
app.use((req, res) => {
res.status(404).json({ error: `Ruta ${req.method} ${req.url} no encontrada` })
})Prueba: GET /peliculas/inexistente-ruta-123 o GET /hola → mensaje de error claro.
Tu index.js completo debe tener estas rutas funcionando:
| Método | Ruta | Descripción |
|---|---|---|
| GET | /peliculas |
Lista todas (con filtro ?genero=) |
| GET | /peliculas/:id |
Una película por ID |
| GET | /estadisticas |
Nota media |
| POST | /peliculas |
Crea una película |
| DELETE | /peliculas/:id |
Elimina una película |
- El servidor arranca sin errores con
npm run dev -
GET /peliculasdevuelve todas las películas en JSON -
GET /peliculas/1devuelve la película correcta -
GET /peliculas/99devuelve status 404 -
GET /peliculas?genero=ciencia-ficcionfiltra correctamente -
POST /peliculascon body completo devuelve status 201 con la nueva película -
POST /peliculassin campos obligatorios devuelve status 400 -
GET /estadisticascalcula la media correctamente -
DELETE /peliculas/:idelimina la película y devuelve status 200 - Rutas inexistentes devuelven status 404 con mensaje descriptivo
Si terminas antes de tiempo:
- Ruta PUT: Implementa
PUT /peliculas/:idque actualice todos los campos de una película - Ruta PATCH: Implementa
PATCH /peliculas/:idque actualice solo los campos enviados en el body (pista: usa el spread operator{ ...pelicula, ...req.body }) - Búsqueda por texto: Añade
GET /peliculas?buscar=nolanque filtre películas cuyo director o título contenga el término buscado (case-insensitive)
