En esta página

Proyecto final — API REST de librería

25 min lectura TextoCap. 5 — Producción

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 joi

Crea 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:4200

La 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
  • ValidationPipe global con whitelist: true y transform: true
  • JwtAuthGuard registrado globalmente con APP_GUARD
  • RolesGuard registrado globalmente después del JwtAuthGuard
  • Swagger configurado y accesible en /docs
  • setGlobalPrefix('api/v1') en main.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.

Semillas de datos para desarrollo
Crea un comando de seeding que inserte datos de ejemplo al arrancar en development. Implementa la interfaz `OnModuleInit` en un `SeedService` y llama a `seed()` solo si la tabla de libros está vacía. Esto hace que el proyecto sea reproducible para cualquier desarrollador que lo clone.
Diagrama de la arquitectura final
La API de la librería tiene esta jerarquía de módulos: AppModule importa AuthModule (JWT, Passport), UsuariosModule, AutoresModule, GenerosModule y LibrosModule. Todos los módulos feature siguen el mismo patrón: Module → Controller → Service → Repository → Entity. Esta consistencia hace el proyecto predecible y fácil de extender.