CRUD con repositorios

Avanzado
Nest
Nest
Actualizado: 09/06/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

Dentro de NestJS, uno de los conceptos más comunes es la creación de servicios que interactúan con bases de datos para realizar operaciones CRUD.

CRUD es un acrónimo de "Create, Read, Update, Delete", que se refiere a las operaciones básicas que se pueden realizar sobre un conjunto de datos.

Para implementar métodos CRUD en repositorios, primero se necesita definir un modelo de datos que represente la estructura de los objetos que se almacenarán en la base de datos. Luego, se crea un repositorio que contiene métodos para realizar las operaciones CRUD en estos objetos.

Creación de un servicio CRUD

Este ejemplo asume que ya tienes TypeORM configurado en tu proyecto NestJS (incluyendo variables de entorno y TypeOrmModule.forRootAsync en AppModule).

1. Crear la entidad Task

Nuestra entidad Task representará la tabla task en la base de datos.

// src/tasks/task.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class Task {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  description: string;

  @Column({ default: false })
  completed: boolean;
}

2. Creación de DTOs (Data Transfer Objects)

Para una API robusta y validable, usamos DTOs (Data Transfer Objects). Son clases que definen la estructura de los datos que se esperan en las peticiones (ej. al crear o actualizar una tarea). Para validación, se suele usar class-validator y class-transformer (deberías instalarlos con npm install class-validator class-transformer).

// src/tasks/dto/create-task.dto.ts
import { IsString, IsBoolean, IsOptional, IsNotEmpty } from 'class-validator';

export class CreateTaskDto {
  @IsNotEmpty() // Asegura que el título no esté vacío
  @IsString()
  title: string;

  @IsNotEmpty() // Asegura que la descripción no esté vacía
  @IsString()
  description: string;

  @IsOptional() // El campo 'completed' es opcional al crear
  @IsBoolean()
  completed?: boolean;
}

// src/tasks/dto/update-task.dto.ts
import { PartialType } from '@nestjs/mapped-types'; // Para crear un DTO parcial fácilmente
import { CreateTaskDto } from './create-task.dto';

// PartialType hace que todas las propiedades de CreateTaskDto sean opcionales
export class UpdateTaskDto extends PartialType(CreateTaskDto) {}

3. Crear el módulo TasksModule

El módulo TasksModule agrupa la entidad Task, su repositorio, el servicio y el controlador. Aquí utilizamos TypeOrmModule.forFeature() para registrar la entidad Task específicamente para este módulo.

// src/tasks/tasks.module.ts
import { Module } from '@nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Task } from './task.entity';

@Module({
  imports: [
    TypeOrmModule.forFeature([Task]), // Registra la entidad Task para este módulo
  ],
  controllers: [TasksController],
  providers: [TasksService],
})
export class TasksModule {}

4. Crear el servicio TasksService

Guarda tu progreso

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

El TasksService contiene la lógica de negocio y utiliza el Repository<Task> de TypeORM para interactuar directamente con la base de datos.

// src/tasks/tasks.service.ts
import { Injectable, NotFoundException } from '@nestjs/common'; // Importa NotFoundException
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Task } from './task.entity';
import { CreateTaskDto } from './dto/create-task.dto';
import { UpdateTaskDto } from './dto/update-task.dto';

@Injectable()
export class TasksService {
  constructor(
    @InjectRepository(Task)
    private tasksRepository: Repository<Task>, // Inyecta el repositorio de Task
  ) {}

  // ** C (Create) **
  async create(createTaskDto: CreateTaskDto): Promise<Task> {
    const newTask = this.tasksRepository.create(createTaskDto); // Crea una instancia de Task
    return this.tasksRepository.save(newTask); // Guarda la tarea en la base de datos
  }

  // ** R (Read) - Obtener todas las tareas **
  findAll(): Promise<Task[]> {
    return this.tasksRepository.find(); // Encuentra y devuelve todas las tareas
  }

  // ** R (Read) - Obtener una tarea por su ID (¡Actualizado para TypeORM 0.3.x!) **
  async findOne(id: number): Promise<Task> {
    const task = await this.tasksRepository.findOne({ where: { id } }); // Busca por ID
    if (!task) {
      throw new NotFoundException(`Task with ID ${id} not found`); // Lanza una excepción si no se encuentra
    }
    return task;
  }

  // ** U (Update) - Actualizar una tarea existente por su ID (¡Lógica mejorada!) **
  async update(id: number, updateTaskDto: UpdateTaskDto): Promise<Task> {
    const taskToUpdate = await this.tasksRepository.findOne({ where: { id } });
    if (!taskToUpdate) {
      throw new NotFoundException(`Task with ID ${id} not found`);
    }

    // Fusiona las propiedades del DTO con la tarea existente
    const updatedTask = this.tasksRepository.merge(taskToUpdate, updateTaskDto);
    return this.tasksRepository.save(updatedTask); // Guarda los cambios
  }

  // ** D (Delete) - Eliminar una tarea por su ID **
  async remove(id: number): Promise<void> {
    const result = await this.tasksRepository.delete(id); // Elimina la tarea por ID
    if (result.affected === 0) { // Verifica si se eliminó alguna fila
      throw new NotFoundException(`Task with ID ${id} not found`);
    }
  }
}

5. Crear el controlador TasksController

El TasksController maneja las peticiones HTTP y delega la lógica de negocio al TasksService. Aquí usamos los DTOs para la validación automática de los cuerpos de las peticiones.

// src/tasks/tasks.controller.ts
import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Param,
  Body,
  HttpCode, // Para usar @HttpCode
  HttpStatus, // Para usar HttpStatus
  ParseIntPipe, // Para validar que el ID sea un número
} from '@nestjs/common';
import { TasksService } from './tasks.service';
import { Task } from './task.entity';
import { CreateTaskDto } from './dto/create-task.dto';
import { UpdateTaskDto } from './dto/update-task.dto';

@Controller('tasks') // Prefijo de ruta para todos los endpoints de este controlador
export class TasksController {
  constructor(private readonly tasksService: TasksService) {}

  // ** C (Create) **
  @Post()
  @HttpCode(HttpStatus.CREATED) // Retorna un 201 Created por defecto, pero es explícito
  create(@Body() createTaskDto: CreateTaskDto): Promise<Task> {
    return this.tasksService.create(createTaskDto);
  }

  // ** R (Read) - Obtener todas las tareas **
  @Get()
  findAll(): Promise<Task[]> {
    return this.tasksService.findAll();
  }

  // ** R (Read) - Obtener una tarea por su ID **
  @Get(':id')
  // Usamos ParseIntPipe para asegurar que el 'id' del parámetro de ruta sea un número
  findOne(@Param('id', ParseIntPipe) id: number): Promise<Task> {
    return this.tasksService.findOne(id);
  }

  // ** U (Update) - Actualizar una tarea existente por su ID **
  @Put(':id')
  update(
    @Param('id', ParseIntPipe) id: number,
    @Body() updateTaskDto: UpdateTaskDto,
  ): Promise<Task> {
    return this.tasksService.update(id, updateTaskDto);
  }

  // ** D (Delete) - Eliminar una tarea por su ID **
  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT) // Retorna un 204 No Content para eliminaciones exitosas
  remove(@Param('id', ParseIntPipe) id: number): Promise<void> {
    return this.tasksService.remove(id);
  }
}

6. Importar TasksModule en AppModule:

Para que NestJS reconozca y cargue nuestro TasksModule, debemos importarlo en el AppModule (nuestro módulo raíz). También nos aseguramos de que la configuración de TypeOrmModule.forRootAsync esté correcta para la detección de entidades.

// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TasksModule } from './tasks/tasks.module';
import { ConfigModule, ConfigService } from '@nestjs/config'; // Asegúrate de importar ConfigModule y ConfigService
import { Task } from './tasks/task.entity'; // Importa la entidad Task si la usas directamente en 'entities'

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: '.env',
    }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => ({
        type: configService.get<"mysql">('DB_TYPE'),
        host: configService.get<string>('DB_HOST'),
        port: configService.get<number>('DB_PORT'),
        username: configService.get<string>('DB_USERNAME'),
        password: configService.get<string>('DB_PASSWORD'),
        database: configService.get<string>('DB_DATABASE'),
        entities: [__dirname + '/**/*.entity{.ts,.js}'], // O si solo quieres Task: [Task]
        synchronize: true, // ¡NUNCA en producción! Usar migraciones.
      }),
      inject: [ConfigService],
    }),
    TasksModule, // Importa nuestro módulo de tareas
  ],
})
export class AppModule {}

7. Archivo main.ts

Finalmente, el archivo main.ts es el punto de entrada de tu aplicación NestJS. Es necesario que esté configurado correctamente para iniciar el servidor.

// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common'; // Importa ValidationPipe

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // Habilita la validación global usando los DTOs y class-validator
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true, // Remueve propiedades que no están definidas en el DTO
    forbidNonWhitelisted: true, // Lanza error si hay propiedades no definidas en DTO
    transform: true, // Transforma los payloads a las instancias del DTO
  }));
  await app.listen(3000);
}
bootstrap();

Probando la Implementación CRUD

Con esta implementación, tienes un CRUD completo de tareas funcionando con NestJS y TypeORM. Puedes probar las rutas utilizando una herramienta como Postman, Insomnia o curl en tu terminal.

  • Obtener todas las tareas: GET http://localhost:3000/tasks
  • Obtener una tarea específica: GET http://localhost:3000/tasks/:id (reemplaza :id con el ID real, ej. http://localhost:3000/tasks/1)
  • Crear una tarea: POST http://localhost:3000/tasks con un objeto JSON en el cuerpo de la solicitud:
{
  "title": "Aprender NestJS",
  "description": "Completar todas las lecciones del curso.",
  "completed": false
}
  • Actualizar una tarea: PUT http://localhost:3000/tasks/:id con un objeto JSON en el cuerpo de la solicitud que contenga las propiedades que deseas actualizar:
{
  "completed": true
}
  • Eliminar una tarea: DELETE http://localhost:3000/tasks/:id (reemplaza :id con el ID real)

Conclusión

Los métodos CRUD en repositorios, implementados a través de servicios y controladores en NestJS, son las operaciones esenciales para interactuar con tu base de datos. Al estructurar tu código con entidades, DTOs, servicios y controladores, no solo realizas estas operaciones básicas de creación, lectura, actualización y eliminación de registros, sino que también construyes aplicaciones web y API RESTful eficientes, escalables y fáciles de mantener.

Aprendizajes de esta lección

  • Comprender el concepto CRUD (Create, Read, Update, Delete) en el contexto de NestJS.
  • Conocer cómo integrar y utilizar TypeORM, particularmente en la creación de repositorios y en la interacción con bases de datos.
  • Aprender a crear un servicio que contenga métodos para realizar operaciones CRUD en la base de datos.
  • Entender cómo configurar y probar rutas HTTP para interactuar con los métodos del servicio y realizar operaciones CRUD a través de las solicitudes HTTP.

Completa Nest y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración