Search code examples
typescriptpostgresqlnestjstypeormnestjs-typeorm

How can I correctly type the return value from a Postgres database in TypeORM with NestJS?


How to correctly type the value returned from the postgres database

Can anyone please explane this moment. I'm new at postgresql, nest and typeorm, and i want to create primitive CRUD application. I have a question about typing at typeorm.

My User Entity:

@Entity('user')
export class UserEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  email: string;

  @Column({
    type: 'varchar',
  })
  name: string;

  @Column({
    type: 'varchar',
  })
  password: string;

  @Column({
    nullable: true,
    type: 'varchar',
    default: () => 'NULL',
  })
  refreshToken: string;
}

My UserService:

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(UserEntity)
    private usersRepository: Repository<UserEntity>,
  ) {}

  createUser(createUserDto: SignUpDto) {
    return this.usersRepository.save(createUserDto);
  }

  findAllUsers() {
    return this.usersRepository.find({
      select: ['id', 'email', 'name'],
    });
  }

  findUserById(id: number) {
    return this.usersRepository
      .createQueryBuilder('user')
      .select([
        'user.id',
        'user.email',
        'user.name',
      ])
      .where('user.id= :userId', { userId: id })
      .getOne();
  }

  findUserByEmail(email: string) {
    return this.usersRepository.findOneBy({ email });
  }
}

FindUserById is external method (there is a controller method that calls it) . It returns a user with incomplete data without password and refreshToken fields ( I don't think they should be exposed).
FindUserByEmail is an internal method that is used by another service (authService). It returns the complete user data.

The problem is that the inferred return type is Promise<UserEntity> in both cases, and I don't see how that could fit both functions.

First, I think the inferred type should be at least Promise<UserEntity | null>. But it's not.

Second, I thought TypeORM would automatically infer the data type. The question is, can TypeORM actually do this? And if not, how do I correctly type return values then? Create multiple different data types? Use Omit? Type UserEntity by adding optional fields? I don't understand.


Solution

  • The problem is that the inferred return type is Promise<UserEntity> in both cases, and I don't see how that could fit both functions. First, I think the inferred type should be at least Promise<UserEntity | null>. But it's not.

    You're correct. The options are either it finds it in the db and resolves with UserEntity, or it doesn't find it and resolves with null.

    UserEntity is an object. Any object can be null. Therefore UserEntity | null is the same as just UserEntity.

    Second, I thought TypeORM would automatically infer the data type. The question is, can TypeORM actually do this? And if not, how do I correctly type return values then? Create multiple different data types? Use Omit? Type UserEntity by adding optional fields? I don't understand.

    TypeORM infers the return type with the assumption that you return the full entity (don't do any selects). Since you know more than TypeORM when writing your UsersService, you're responsible for specifying more precise type information if you want to. Partial/Omit/Pick are good options for this.