No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Crear, actualizar y eliminar

12/33
Recursos

Aportes 8

Preguntas 3

Ordenar por:

Los aportes, preguntas y respuestas son vitales para aprender en comunidad. Reg铆strate o inicia sesi贸n para participar.

Implementaci贸n de un servicio gen茅rico

Se me ocurri贸 implementar un servicio gen茅rico que contenga todos los m茅todos CRUD utilizando los generics de TypeScript.
Con esto ganamos que solo debemos implementar una clase gen茅rica y todas los dem谩s servicios de esta clase deber谩n extender de esta clase gen茅rica, as铆 tendr谩n todos los m茅todos CRUD implementados de forma autom谩tica y con el m铆nimo esfuerzo.

Para comenzar implementamos una nueva clase dentro de la carpeta common, la llamar茅 GenericService.service.ts
Esta clase recibir谩 3 Generics Types:

  • ENTITY: Representa la entidad del servicio
  • ID: Representa el tipo del Primary Key del Repositorio (no siempre es un number, por eso lo coloco como din谩mico)
  • DTO: Representa la clase del DTO (necesaria para recibir los datos desde el body)
  • PARTIAL_DTO: Representa la misma clase del DTO pero con sus atributos como opcionales

La clase es abstracta para evitar que la implementen por error.

export abstract class GenericService<ENTITY, ID, DTO, PARTIAL_DTO>  {}

Adem谩s, esta clase recibe en su constructor una instancia de un repositorio gen茅rico (esta ser谩 enviada desde la implementaci贸n de esta clase)

export abstract class GenericService<ENTITY, ID, DTO, PARTIAL_DTO>  {
    private readonly genericRepository: Repository<ENTITY>
    constructor(genericRepository: Repository<ENTITY>) {
        this.genericRepository = genericRepository;
    }
}

Una vez echo esto ya podemos crear los m茅todos CRUD gen茅ricos:

async create(data: DTO): Promise<ENTITY> {
        const newItem = this.genericRepository.create(data);
        await this.genericRepository.save(newItem);
        return newItem;
    }

El c贸digo completo ser铆a el siguiente:

import { NotFoundException } from '@nestjs/common';
import { Repository } from "typeorm";

export abstract class GenericService<ENTITY, ID, DTO, PARTIAL_DTO>  {
    private readonly genericRepository: Repository<ENTITY>
    constructor(genericRepository: Repository<ENTITY>) {
        this.genericRepository = genericRepository;
    }

    async create(data: DTO): Promise<ENTITY> {
        const newItem = this.genericRepository.create(data);
        await this.genericRepository.save(newItem);
        return newItem;
    }

    async update(id: ID, data: PARTIAL_DTO): Promise<ENTITY> {
        const product = await this.genericRepository.findOne(id);
        if (!product) {
            throw new NotFoundException(`Product with id ${id} not found`);
        }
        return this.genericRepository.merge(product, data);
    }

    async delete(id: ID): Promise<boolean> {
        const foundItem = await this.findOne(id);
        await this.genericRepository.delete(id);
        return true;
    }

    async findAll(): Promise<ENTITY[]> {
        return this.genericRepository.find();
    }

    async findOne(id: ID): Promise<ENTITY> {
        const product = await this.genericRepository.findOne(id);
        if (!product) {
            throw new NotFoundException(`Product with id ${id} not found`);
        }
        return product;
    }
}

Ahora para utilizar esta clase gen茅rica solo debemos extender nuestros servicios normalmente e indicamos los valores de los tipos gen茅ricos, por ejemplo, el servicio de Productos ser铆a solo as铆:

@Injectable()
export class ProductsService extends GenericService<Product, number, CreateProductDto, UpdateProductDto> {
  constructor(@InjectRepository(Product) private productRepository: Repository<Product>) {
    super(productRepository);
  }
}

El servicio de usuarios ser铆a igual:

@Injectable()
export class UsersService extends GenericService<Client, number, CreateUserDto, UpdateUserDto>{
  constructor(
    @InjectRepository(Client) private clientRepository: Repository<Client>,
    private productsService: ProductsService,
    private configService: ConfigService,
  ) {
    super(clientRepository);
  }
  //all code here
}

De esta forma como se puede observar el c贸digo se reutiliza muy 贸iptimamente y no tenemos que esta repitiendo las operaciones CRUD una y otra vez en cada servicio de la aplicaci贸n.

Yo lo hice as铆:

 //src/products/services/products.service.ts
 
 async findOne(id: number) {
    const product = await this.productRepo.findOne(id);
    if (!product) {
      throw new NotFoundException(`Product #${id} not found`);
    }
    return product;
 }

 async remove(id: number) {
    await this.findOne(id);
    return this.productRepo.delete(id);
 } 
 //src/products/controllers/products.controller.ts
 @Delete(':id')
 async delete(@Param('id', ParseIntPipe) id: number) {
    await this.productsService.remove(id);
    return {
      message: `Producto #${id} eliminado`,
    };
 }

Delete ok
Create ok
update ok

create(data: CreateProductDto) {
    const newProduct = {
      ...data,
    };  
    this.productRepository.create(newProduct);//No estoy seguro si eliminar esta linea, funciona sin ella pero no estoy seguro
    return this.productRepository.save(newProduct);
  }

async update(id: number, changes: UpdateProductDto) {
    const product = await this.productRepository.findOne(id);
    this.productRepository.merge(product, changes)
    return this.productRepository.save(product);
  }

async remove(id: number) {
    const index = await this.productRepository.findOne(id);
    this.productRepository.delete(index);
    return true;
  }

Les dejo el remove con la busqueda antes de intentar eliminar.

async remove(id: number) {
    //Si no existe, damos error.
    if (!(await this.findOne(id))) {
      throw new NotFoundException();
    }
    return this.productRepo.delete(id);
  }

Le agregue una validaci贸n al create para que no de un error 500.

async findOne(id: number) {
    const product = await this.productRepo.findOne(id);
    if (!product) {
      throw new NotFoundException(`Product with id ${id} not found`);
    }
    return product;
  }

  create(payload: CreateProductDto) {
    const newProduct = this.productRepo.create(payload);
    const product = this.productRepo
      .save(newProduct)
      .then((res) => {
        return res;
      })
      .catch((err) => {
        throw new BadRequestException(`${err.message || 'Unexpected Error'}`);
      });

    return product;
  }

  async update(id: number, payload: UpdateProductDto) {
    const product = await this.findOne(id);
    this.productRepo.merge(product, payload);
    return this.productRepo.save(product);
  }

  async delete(id: number) {
    await this.findOne(id);
    this.productRepo.delete(id);
    return {
      message: `Product with id ${id} deleted`,
    };
  }

No habia terminado de ver el video, al hacer el await en el findOne ya no es necesario usarlo en el remove, el codigo quedaria asi鈥

remove(id: number) {
    //Si no existe, damos error.
    if (this.findOne(id)) {
      throw new NotFoundException();
    }
    return this.productRepo.delete(id);
  }

Crear, actualizar y eliminar

//src/products/services/products.service.ts

@Injectable()
export class ProductsService {

......


async findOne(id: number) {
    const product = await this.productRepo.findOne(id); // 馃憟 use repo
    if (!product) {
      throw new NotFoundException(`Product #${id} not found`);
    }
    return product;
  }

  create(data: CreateProductDto) {
    // Forma 1 - Declarando un por uno
    // const newProduct = new Product();
    // newProduct.name = data.image;
    // newProduct.description = data.description;
    // newProduct.price = data.price;
    // newProduct.stock = data.stock;
    // newProduct.image = data.image;

    // Forma 1 - Creando un instancia
    // De esta manera podemos instancia de manera mas facil todos los elementos de un producto
    const newProduct = this.productRepo.create(data);
    return this.productRepo.save(newProduct); // Guradamos en la base de datos
  }

  async update(id: number, changes: UpdateProductDto) {
    const product = await this.productRepo.findOne(id); // Obtenemos el producto a actualizar
    this.productRepo.merge(product, changes);

    return this.productRepo.save(product); // Guradamos en la base de datos
  }

  remove(id: number) {
    return this.productRepo.delete(id);
  }
}
//src/products/controllers/products.controller.ts

@Controller('products')
export class ProductsController {

.......

  @Post()
  create(@Body() payload: CreateProductDto) {
    return this.productsService.create(payload);
  }

  @Put(':id')
  update(@Param('id') id: number, @Body() payload: UpdateProductDto) {
    return this.productsService.update(id, payload);
  }

  @Delete(':id')
  delete(@Param('id') id: number) {
    return this.productsService.remove(id);
  }
}
//src/products/dtos/products.dtos.ts
.....
import { PartialType, ApiProperty } from '@nestjs/swagger';

export class CreateProductDto {
  @IsString()
  @IsNotEmpty()
  @ApiProperty({ description: `product's name` }) // 馃憟 new Decorator
  readonly name: string;

  @IsString()
  @IsNotEmpty()
  @ApiProperty() // 馃憟 new Decorator
  readonly description: string;

  @IsNumber()
  @IsNotEmpty()
  @IsPositive()
  @ApiProperty() // 馃憟 new Decorator
  readonly price: number;

  @IsNumber()
  @IsNotEmpty()
  @ApiProperty() // 馃憟 new Decorator
  readonly stock: number;

  @IsUrl()
  @IsNotEmpty()
  @ApiProperty() // 馃憟 new Decorator
  readonly image: string;
}

export class UpdateProductDto extends PartialType(CreateProductDto) {}

Lo que vimos:

products.controller.ts

async findOne(id: number) {
    const product = await this.productRepo.findOne(id);
    if (!product) {
      throw new NotFoundException(`Product #${id} not found`);
    }
    return product;
  }
 create(data: CreateProductDto) {
    const newProduct = this.productRepo.create(data);
    return this.productRepo.save(newProduct);
  }

  async update(id: number, changes: UpdateProductDto) {
    const product = await this.productRepo.findOne(id);
    this.productRepo.merge(product, changes);
    return this.productRepo.save(product);
  }

  remove(id: number) {
    return this.productRepo.delete(id);
  }

products.dtos.ts

export class CreateProductDto {
  @IsString()
  @IsNotEmpty()
  @ApiProperty({ description: `product's name` })
  readonly name: string;

  @IsString()
  @IsNotEmpty()
  @ApiProperty()
  readonly description: string;

  @IsNumber()
  @IsNotEmpty()
  @IsPositive()
  @ApiProperty()
  readonly price: number;

  @IsNumber()
  @IsNotEmpty()
  @ApiProperty()
  readonly stock: number;

  @IsUrl()
  @IsNotEmpty()
  @ApiProperty()
  readonly image: string;
}