En esta página
Proyecto final — API REST de librería
El proyecto final: API de gestión de librería
En esta lección de playground construirás una API REST completa para gestionar una librería. Este proyecto integra todos los conceptos vistos en el curso:
- Módulos: Estructura organizada con módulos de funcionalidad
- TypeORM: Entidades con relaciones ManyToOne y ManyToMany
- Autenticación JWT: Registro, login y protección de rutas
- Autorización por roles: Admin, editor y usuario
- Validación: class-validator con DTOs completos
- Paginación y filtros: QueryBuilder dinámico
- Swagger: Documentación completa con OpenAPI
Arquitectura del proyecto
El proyecto tiene cinco dominios principales:
| Dominio | Rutas | Roles |
|---|---|---|
| Auth | /auth/registro, /auth/login |
Público |
| Autores | /autores CRUD |
Público (GET), Admin/Editor (POST, PUT, DELETE) |
| Géneros | /generos CRUD |
Público (GET), Admin (POST, PUT, DELETE) |
| Libros | /libros CRUD + stock |
Público (GET), Admin/Editor (POST, PATCH, DELETE) |
| Usuarios | /usuarios/perfil |
Autenticado |
Configuración inicial del proyecto
# Crear el proyecto
nest new libreria-api
cd libreria-api
# Instalar dependencias
npm install @nestjs/typeorm typeorm pg @nestjs/config @nestjs/passport @nestjs/jwt
npm install passport passport-local passport-jwt bcrypt class-validator class-transformer
npm install @nestjs/swagger swagger-ui-express
npm install --save-dev @types/passport-local @types/passport-jwt @types/bcrypt
npm install joiCrea el .env:
NODE_ENV=development
PORT=3000
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASS=secreto
DB_NAME=libreria_db
JWT_SECRET=super_secreto_de_al_menos_32_caracteres_aqui_ok
JWT_EXPIRES_IN=1h
FRONTEND_URL=http://localhost:4200La entidad Género
// generos/entities/genero.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToMany } from 'typeorm';
import { ApiProperty } from '@nestjs/swagger';
import { Libro } from '../../libros/entities/libro.entity';
@Entity('generos')
export class Genero {
@ApiProperty()
@PrimaryGeneratedColumn('uuid')
id: string;
@ApiProperty({ example: 'Realismo mágico' })
@Column({ unique: true, length: 100 })
nombre: string;
@ApiProperty({ example: 'realismo-magico' })
@Column({ unique: true, length: 120 })
slug: string;
@ManyToMany(() => Libro, (libro) => libro.generos)
libros: Libro[];
}El módulo de autores
// autores/dto/crear-autor.dto.ts
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsString, IsOptional, IsISO8601, MaxLength } from 'class-validator';
export class CrearAutorDto {
@ApiProperty({ example: 'Isabel Allende' })
@IsString()
@MaxLength(150)
nombre: string;
@ApiProperty({ example: 'Chile' })
@IsString()
@MaxLength(100)
nacionalidad: string;
@ApiPropertyOptional({ example: 'Escritora chilena nacida en Lima...' })
@IsOptional()
@IsString()
biografia?: string;
@ApiPropertyOptional({ example: '1942-08-02' })
@IsOptional()
@IsISO8601({ strict: true })
fechaNacimiento?: string;
}Extendiendo el proyecto — ideas de funcionalidades adicionales
Una vez que tengas la API base funcionando, aquí hay funcionalidades que puedes añadir para seguir practicando:
1. Sistema de reseñas y puntuaciones:
@Entity('resenas')
export class Resena {
@PrimaryGeneratedColumn('uuid') id: string;
@Column({ type: 'int' }) puntuacion: number; // 1-5
@Column({ type: 'text' }) comentario: string;
@ManyToOne(() => Libro, libro => libro.resenas) libro: Libro;
@ManyToOne(() => Usuario, usuario => usuario.resenas) usuario: Usuario;
@CreateDateColumn() creadoEn: Date;
}2. Sistema de favoritos/lista de deseos:
@Entity('favoritos')
export class Favorito {
@ManyToOne(() => Usuario) usuario: Usuario;
@ManyToOne(() => Libro) libro: Libro;
@CreateDateColumn() agregadoEn: Date;
}3. Estadísticas del catálogo:
@Get('estadisticas')
@Roles('admin')
async getEstadisticas() {
const [totalLibros, librosAgotados, totalAutores, precioPromedio] = await Promise.all([
this.libroRepo.count(),
this.libroRepo.count({ where: { estado: EstadoLibro.AGOTADO } }),
this.autorRepo.count(),
this.libroRepo.average('precio'),
]);
return { totalLibros, librosAgotados, totalAutores, precioPromedio };
}4. Exportar catálogo en CSV:
@Get('exportar/csv')
@Roles('admin')
async exportarCsv(@Res() res: Response) {
const libros = await this.libroRepo.find({
relations: { autor: true, generos: true },
});
const csv = [
'ISBN,Título,Autor,Precio,Stock,Estado',
...libros.map(l =>
`"${l.isbn}","${l.titulo}","${l.autor.nombre}",${l.precio},${l.stock},${l.estado}`
),
].join('\n');
res.set({
'Content-Type': 'text/csv; charset=utf-8',
'Content-Disposition': 'attachment; filename="catalogo.csv"',
});
res.send('\uFEFF' + csv); // BOM para Excel
}Checklist de la API completa
Antes de considerar el proyecto terminado, verifica que tienes:
- Todos los módulos registrados en
AppModule -
ValidationPipeglobal conwhitelist: trueytransform: true -
JwtAuthGuardregistrado globalmente conAPP_GUARD -
RolesGuardregistrado globalmente después delJwtAuthGuard - Swagger configurado y accesible en
/docs -
setGlobalPrefix('api/v1')enmain.ts - Variables de entorno validadas con Joi
- Al menos un test unitario por servicio
- Manejo de errores con excepciones HTTP apropiadas
- Soft delete en entidades (con
@DeleteDateColumn) - Índices en columnas frecuentemente consultadas
¡Felicitaciones por completar el curso de NestJS Completo! Has construido desde cero una API REST de nivel profesional con todas las mejores prácticas del ecosistema NestJS. El siguiente paso es desplegar tu API en un entorno real —considera plataformas como Railway, Render o AWS Elastic Container Service con Docker.
Inicia sesión para guardar tu progreso