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.
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