Search code examples
typescriptpostgresqlnestjsbackendcrud

How to update fields of an Entitiy partially (Nest.js ,typescript, typeorm, postgres, sql)


async updateOne(
    customerId: string,
    name: string,
    legalStatus: LegalStatus,
    legalRegistrationDate: Date,
    address: string,
    city: City,
    businessPhone: string,
    businessEmail: string,
    businessWebsite: string,
    businessType: BusinessType,
    activityStartingDate: Date,
    fullTimeEmployees: number,
    partTimeEmployees: number,
    yearlyTurnover: number,
    otherInfo: string
  ) {
    const customer = await this.customersRepository.findOne(customerId);

    if (!customer) {
      throw new HttpException('Failed to find the Customer with given id', HttpStatus.NOT_FOUND);
    }

    if (name) {
      const { id } = customer;
      const { name } = customer;

      const customers = await this.customersRepository.find({
        where: {
          id: Not(id),
          name,
        },
      });

      if (customers.length > 0) {
        throw new HttpException(
          'The Customer with the given name already exists',
          HttpStatus.BAD_REQUEST
        );
      }
    }

    const payload = {
      name,
      legalStatus,
      legalRegistrationDate,
      address,
      city,
      businessPhone,
      businessEmail,
      businessWebsite,
      businessType,
      activityStartingDate,
      fullTimeEmployees,
      partTimeEmployees,
      yearlyTurnover,
      otherInfo,
    };

    console.log(payload);

    // const updatedCustomer = Object.assign(customer, payload);

    const updatedCustomer = this.customersRepository.update(customer.id, payload);

    if (!updatedCustomer) {
      throw new HttpException('Failed to update the Customer', HttpStatus.INTERNAL_SERVER_ERROR);
    }

    const savedCustomer = this.customersRepository.save(customer);

    if (!savedCustomer) {
      throw new HttpException('Failed to save the Customer', HttpStatus.INTERNAL_SERVER_ERROR);
    }

    return savedCustomer;
  }

I am using nest.js, typescript, typeorm, postgress sql. I want to update specific fields of entity, and i want fields that i have not entered not to update. Is there any method i could use to update entity partially and not whole. I know a method Partial< EntityName > but it doesn't work with object as fields. I want a solution to this problem if someone can find it.


Solution

  • There are some solutions. Unfortunately, what you're describing is more of a domain of OOP IMO. So let's divide this problem into subproblems:

    1. So we could safely create a generic update solution, we need to ensure that a passed object is really of a given type. I'm not experienced with solutions such as ZOD (although I'm looking into it), so let's do a straightforward classes to demonstrate the problem. Also, I do not accept validation at the level of RestController. This is really troublesome. According to the best practices on the API design I know, a Service Layer is an actual API, Rest Controller is just a PORT. Ok, that being said let's consider following interface and class:
    export interface IUser {
        id: number,
        username: string,
        firstName: string
    }
    
    export class User implements IUser {
        id: number;
        username: string;
        firstName: string;
    
        // this is how I prefer doing constructors then:
        constructor(iUser: IUser) {
            this.id = iUser.id;
            this.username = iUser.username;
            this.firstName = iUser.firstName;
            
            // here you can do additional validation using for example class-validator with annotations
        }
    }
    

    So far so good. How do we ensure no one passes some malicious fields then? I really hate this about TS. What's the point of strongly typed language if I can still do:

    (user as any).password="now your password is gone"
    

    So what I do in that case is enforcing the type strictly in the part of the code I have full control over:

    //...some service
    updateUser(user: User) {
        user = user instanceof User ? user : new User(user)
        //... the rest of the code
    }
    

    it's a single line some JS developers would consider completely redundant, but I come from Java world, and I can't stand that a type is really a loose type in TS. You can look here for more on interfaces and how unreliable they can be.

    1. Now that we have a proper type, we can make some assumptions about it. What I like to do is assume this:
    • if a field is null -> it should be nulled
    • if a field is undefined -> it should be omitted

    Using this approach you can implement a function doing something like this:

    export abstract class Updatable<T> {
      updatePartial(input: Partial<T>): Partial<Omit<T, "id">> {
        const updateObj = Object.keys(this).reduce(
          (prev, curr) =>
            input[curr] !== undefined ? { ...prev, [curr]: input[curr] } : prev,
          {},
        );
        // just in case
        delete (updateObj as any).id;
        return updateObj;
      }
    }
     
    

    which basically goes through the fields of an entity and compares them with given input. You should ensure names of the input match the entities'.

    Then simply create a UserEntity and make it extend Updatable.

    This solution is probably far from ideal, but if you want to perform safe PATCH operations, this is as far as I got it. If you find a better solution, please let me know, I'd love to make it better :)