LoginSignup
1
0

More than 1 year has passed since last update.

NestJS x TypeScript x Mongoose で、インスタンス生成時に Type Check が行われるようにする

Posted at

NestJS の MongoDB 利用方法マニュアルにある通り、こんなスキーマがあったとして、

// cats.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';

@Schema()
export class Cat {
  @Prop({
    type: String,
    required: true,
  })
  name: string;

  @Prop({
    type: Number,
    required: true,
  })
  age: number;
}

export type CatDocument = HydratedDocument<Cat>;
export const CatSchema = SchemaFactory.createForClass(Cat);

インスタンスを生成しようとする。

// cats.repository.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Cat, PreRegisterCat } from './cat.entity';
import { CatDocument } from './cats.schema';

@Injectable()
export class CatsRepository {
  constructor(
    @InjectModel(Cat.name)
    private readonly CatModel: Model<CatDocument>,
  ) {}

  async create() {
    const cat = new this.CatModel({
      name: 'nyan',
      age: 10,
    });
    await cat.save();
  }
}

と、 const cat = new this.CatModel({ name: 'nyan', age: 10 }); の部分で適当な値を代入しても、 TypeScript でのチェックは効かない。もちろん、必須項目が未設定であれば、後続の cat.save() 時にランタイムエラーとして Validation Error が Throw される。( cat.validate() で事前チェックも可能 )

new するときの型チェックを有効にする方法

InferSchemaTypeModel への generics で対応できた。

// cats.schema.ts
  // 略
-  import { HydratedDocument } from 'mongoose';
+  import { HydratedDocument, InferSchemaType } from 'mongoose';
  // 略
  export const CatSchema = SchemaFactory.createForClass(Cat);
+ export type CatPayload = InferSchemaType<typeof CatSchema>;
  // => {
  //  name: string;
  //  age: number;
  // }

として、 repository では

-  import { CatDocument } from './cats.schema';
+  import { CatDocument, CatPayload } from './cats.schema';
  // 略
  async create() {
-    const cat = new this.CatModel({
+    const cat = new this.CatModel<CatPayload>({
        name: 'nyan',
        age: 10,
      });
      await cat.save();
    }
  // 略

という形で指定すれば良い。

存在しないフィールドを指定すると ts でエラーになる。

const cat = new this.CatModel<CatPayload>({ name: 'nyan', age: 10, microchipping: false });
//                                                                 ~~~~~~~~~~~~~~~~~~~~
// Argument of type '{ name: string; age: number; microchipping: boolean; }' is not assignable to parameter of type 'Cat'.
// Object literal may only specify known properties, and 'microchipping' does not exist in type 'Cat'

注意点として、あくまで Type Check しているだけなので、 MongoDB 側で Schema 定義と異なる制約が設定されていたら TypeCheck 上エラーにはならないが、 save() 時にランタイムエラーが発生する (当たり前だしこれは RDB でも一緒) 。

Mongoose のインスタンス生成時にエラーを発生させるのではなく、データの永続化のタイミングでランタイムエラーを発生させるという挙動自体は JavaScript や Ruby のようなインタプリタ言語であれば扱いやすいので、このような仕様になるのは仕方がない。まずからのインスタンスを作ってからちょっとずつフィールドを足していって最終的に valid な状態になったら save したほうが良いケースもあるので、Type Check させたいかどうかはユースケース次第。

他に良い方法があれば教えて下さい。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0