NestJS에서 TypeORM과 TypeScript를 함께 사용하고있다.
보통 그럴 때 tsconfig.json 파일에서 "strict": true (또는 개별 옵션인 "strictNullChecks": true)를 활성화하게 되는데, 이 때 Entity 정의 단계에서부터 "Property '...' has no initializer and is not definitely assigned in the constructor." 라는 타입 에러가 발생했다.
(프로퍼티 '...'에 초기화 코드가 없고 생성자에서 확실히 할당되지 않았습니다.)

그래서 임시방편으로 strict모드를 false로 두고 개발했는데, 확실히 true로 두는 것이 좋을 것 같아 수정하니 Entity에서 우후죽순으로 오류가 발생하였다..ㅎ
@CreateDateColumn({ nullable: false })
create_date: Date;
create_date는 데이터베이스가 자동으로 생성해 주며, 당연히 NULL 값이 들어가면 안 되므로 nullable: false를 설정했다.
그런데 오류가 발생한다... 문제가 뭐고 어떻게 해결할까?
create_date는 NULL이면 안 된다. nullable: false로 설정해주었다.strictNullChecks 때문에 발생하는 TypeScript 컴파일 에러를 피하고 싶다."strict": true 옵션은 TypeScript 컴파일러가 null과 undefined를 모든 타입에서 기본적으로 허용하지 않도록 강제한다.
아래의 코드를 보자.
@Entity('hotel')
export class Hotel {
@Column({ nullable: false })
title: string;
...
}
TypeScript는 이 코드를 보고 이렇게 생각한다
title 프로퍼티는 string 타입으로 선언되었다. 즉, null이나 undefined가 될 수 없다. 그런데 개발자가 new Hotel()처럼 이 클래스의 새 인스턴스를 생성하면, title에 값을 할당하는 코드가 없으니 title은 undefined 상태가 된다. undefined는 string 타입에 할당될 수 없으므로, 이건 에러다!
DB 무결성과 타입 안정성을 모두 잡는 올바른 방법은 nullable 값에 따라 두 가지 규칙을 명확하게 적용하는 것이다.
! 연산자는 TypeScript 컴파일러에게 "이 프로퍼티는 내가 생성자에서 지금 당장 초기화하진 않았지만, 런타임에 다른 누군가(TypeORM)가 반드시 값을 할당해 줄 것이니 너는 에러로 취급하지 말라"고 알려주는 표시다.
nullable: false인 컬럼은 DB에서 조회될 때 반드시 값이 존재함을 의미한다. 따라서 !를 사용하는 것이 의미상 완벽하게 일치한다.
https://www.typescriptlang.org/docs/handbook/2/classes.html
위의 링크에서 Definite Assignment Assertion 검색어로 Ctrl+F 해서 읽어보자
@PrimaryGeneratedColumn()
hotel_id!: number; // DB가 자동 생성
@CreateDateColumn({ nullable: false })
create_date!: Date; // DB가 자동 생성
@Column({ type: 'varchar', nullable: false })
title!: string; // NOT NULL 컬럼
@OneToMany(() => Review, review => review.hotel)
reviews!: Review[]; // 관계(Relation)도 마찬가지
이 방식은 다음 경우에 모두 적용된다.
@PrimaryGeneratedColumn, @CreateDateColumn, @UpdateDateColumn 등 DB가 자동 생성하는 값@Column({ nullable: false })로 명시한 모든 필수 컬럼@OneToMany, @ManyToOne 등 관계 컬럼 (조인 시 값이 할당되므로)Kakao OAuth를 통해서 DB에 값을 넣어주는 아래의 세 컬럼에 대해서는?
// 아래 3개의 컬럼은 kakao OAuth를 통해서 값을 받아옴 @Column({type: 'varchar', length: 20, nullable: false}) nickname!: string; @Column({type: 'varchar', length: 20, nullable: false}) name!: string; @Column({ type: 'varchar', length: 50, nullable: false, unique: true }) email!: string;데이터가 어디서 오느냐(OAuth, 직접 입력 등)가 아니라, 내 서비스의 DB 정책이 어떠한가를 생각해봐야한다.
Kakao로 가입하더라도 nickname, name, email을 필수값으로 받기로 설정했기 때문에, DB 컬럼을nullable: false로 설정했고, 그랬기 때문에 ! (확정적 할당 단언)을 사용했다.
nullable: true 컬럼엔 '| null = null'그렇다면 NULL 값을 허용하는 nullable: true 컬럼은 어떨까?
아래 컬럼은 선택적으로 입력받는 email 컬럼이다.
null 허용 가능이니까 null도 가능하다고 설정해주면 되지 않을까?
// 똑같이 오류가 난다
@Column({ type: 'varchar', length: 50, nullable: true, unique: true })
email: string | null;
DB 스키마(nullable: true)와 타입(string | null)은 일치한다. 하지만 이 코드 역시 new User()로 인스턴스를 생성하는 시점에는 email이 undefined가 되어 "has no initializer..." 에러를 발생시킨다. undefined는 string | null 타입에 속하지 않기 때문이다.
따라서 null로 초기값을 할당 해주자!
@Column({ type: 'varchar', length: 50, nullable: true, unique: true })
email: string | null = null; // <-null 초기값 할당
@Column({ type: 'decimal', precision: 8, scale: 2, nullable: true })
service_fee: number | null = null; // <- null 초기값 할당
이렇게 하면 new User()로 인스턴스를 생성하는 순간 email은 null 값을 가지게 되며, 이는 string | null 타입을 완벽하게 만족시킨다.