TypeScriptにおけるスキーマ
Mongoose スキーマ は、ドキュメントの構造をMongooseに指示する方法です。MongooseスキーマはTypeScriptインターフェースとは別物であるため、生のドキュメントインターフェースとスキーマの両方を定義する必要があります。または、Mongooseがスキーマ定義から型を自動的に推論することに依存します。
自動型推論
Mongooseは、以下の通り、スキーマ定義からドキュメント型を自動的に推論できます。スキーマとモデルを定義する際には、自動型推論に依存することをお勧めします。
import { Schema, model } from 'mongoose';
// Schema
const schema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true },
avatar: String
});
// `UserModel` will have `name: string`, etc.
const UserModel = mongoose.model('User', schema);
const doc = new UserModel({ name: 'test', email: 'test' });
doc.name; // string
doc.email; // string
doc.avatar; // string | undefined | null
自動型推論を使用する場合、いくつかの注意点があります。
tsconfig.json
でstrictNullChecks: true
またはstrict: true
を設定する必要があります。または、コマンドラインでフラグを設定する場合は、--strictNullChecks
または--strict
を使用します。厳格モードが無効になっている場合、自動型推論には既知の問題があります。new Schema()
呼び出しでスキーマを定義する必要があります。スキーマ定義を一時変数に代入しないでください。const schemaDefinition = { name: String }; const schema = new Schema(schemaDefinition);
のようなことをすると動作しません。- スキーマで
timestamps
オプションを指定した場合、MongooseはcreatedAt
とupdatedAt
をスキーマに追加しますが、methods
、virtuals
、またはstatics
も指定した場合は例外です。タイムスタンプとmethods/virtuals/staticsオプションを組み合わせた型推論には既知の問題があります。methods、virtuals、staticsを使用する場合は、createdAt
とupdatedAt
をスキーマ定義に追加する必要があります。
スキーマを別途定義する必要がある場合は、as const(const schemaDefinition = { ... } as const;
)を使用して、型の拡張を防ぎます。TypeScriptは、required: false
のような型をrequired: boolean
に自動的に拡張しますが、これによりMongooseはフィールドが必須であると想定します。as const
を使用すると、TypeScriptはこれらの型を保持します。
スキーマ定義から生のドキュメント型(doc.toObject()
、await Model.findOne().lean()
などから返される値)を明示的に取得する必要がある場合は、MongooseのinferRawDocType
ヘルパーを以下のように使用できます。
import { Schema, InferRawDocType, model } from 'mongoose';
const schemaDefinition = {
name: { type: String, required: true },
email: { type: String, required: true },
avatar: String
} as const;
const schema = new Schema(schemaDefinition);
const UserModel = model('User', schema);
const doc = new UserModel({ name: 'test', email: 'test' });
type RawUserDocument = InferRawDocType<typeof schemaDefinition>;
useRawDoc(doc.toObject());
function useRawDoc(doc: RawUserDocument) {
// ...
}
自動型推論がうまくいかない場合は、いつでもドキュメントインターフェース定義に戻ることができます。
個別のドキュメントインターフェース定義
自動型推論がうまくいかない場合は、以下のように個別の生のドキュメントインターフェースを定義できます。
import { Schema } from 'mongoose';
// Raw document interface. Contains the data type as it will be stored
// in MongoDB. So you can ObjectId, Buffer, and other custom primitive data types.
// But no Mongoose document arrays or subdocuments.
interface User {
name: string;
email: string;
avatar?: string;
}
// Schema
const schema = new Schema<User>({
name: { type: String, required: true },
email: { type: String, required: true },
avatar: String
});
デフォルトでは、Mongooseは生のドキュメントインターフェースがスキーマと一致するかどうかを確認しません。たとえば、上記のコードでは、ドキュメントインターフェースでemail
がオプションでも、schema
でrequired
になっていてもエラーは発生しません。
ジェネリックパラメーター
TypeScriptのMongoose Schema
クラスには、9つのジェネリックパラメーターがあります。
RawDocType
- MongoDBにデータがどのように保存されるかを記述するインターフェースTModelType
- Mongooseモデルの型。クエリヘルパーやインスタンスメソッドを定義しない場合は省略できます。- デフォルト:
Model<DocType, any, any>
- デフォルト:
TInstanceMethods
- スキーマのメソッドを含むインターフェース。- デフォルト:
{}
- デフォルト:
TQueryHelpers
- スキーマに定義されたクエリヘルパーを含むインターフェース。デフォルトは{}
。TVirtuals
- スキーマに定義された仮想フィールドを含むインターフェース。デフォルトは{}
TStaticMethods
- モデルのメソッドを含むインターフェース。デフォルトは{}
TSchemaOptions
-Schema()
コンストラクターの2番目のオプションとして渡される型。デフォルトはDefaultSchemaOptions
。DocType
- スキーマから推論されたドキュメント型。THydratedDocumentType
- ハイドレートされたドキュメント型。これは、await Model.findOne()
、Model.hydrate()
などのデフォルトの戻り値の型です。
TypeScript定義の表示
export class Schema<
RawDocType = any,
TModelType = Model<RawDocType, any, any, any>,
TInstanceMethods = {},
TQueryHelpers = {},
TVirtuals = {},
TStaticMethods = {},
TSchemaOptions = DefaultSchemaOptions,
DocType = ...,
THydratedDocumentType = HydratedDocument<FlatRecord<DocType>, TVirtuals & TInstanceMethods>
>
extends events.EventEmitter {
// ...
}
最初のジェネリックパラメーターであるDocType
は、MongooseがMongoDBに保存するドキュメントの型を表します。Mongooseは、ドキュメントミドルウェアのthis
パラメーターなどの場合に、DocType
をMongooseドキュメントでラップします。例えば、
schema.pre('save', function(): void {
console.log(this.name); // TypeScript knows that `this` is a `mongoose.Document & User` by default
});
2番目のジェネリックパラメーターであるM
は、スキーマで使用されるモデルです。Mongooseは、スキーマで定義されたモデルミドルウェアでM
型を使用します。
3番目のジェネリックパラメーターであるTInstanceMethods
は、スキーマで定義されたインスタンスメソッドの型を追加するために使用されます。
4番目のパラメーターであるTQueryHelpers
は、チェーン可能なクエリヘルパーの型を追加するために使用されます。
スキーマとインターフェースのフィールド
Mongooseは、スキーマ内のすべてのパスがドキュメントインターフェースに定義されていることを確認します。
たとえば、以下のコードは、email
がスキーマ内のパスであるが、DocType
インターフェースにはないため、コンパイルに失敗します。
import { Schema, Model } from 'mongoose';
interface User {
name: string;
email: string;
avatar?: string;
}
// Object literal may only specify known properties, but 'emaill' does not exist in type ...
// Did you mean to write 'email'?
const schema = new Schema<User>({
name: { type: String, required: true },
emaill: { type: String, required: true },
avatar: String
});
しかし、Mongooseは、ドキュメントインターフェースには存在するが、スキーマには存在しないパスについては確認しません。たとえば、以下のコードはコンパイルされます。
import { Schema, Model } from 'mongoose';
interface User {
name: string;
email: string;
avatar?: string;
createdAt: number;
}
const schema = new Schema<User, Model<User>>({
name: { type: String, required: true },
email: { type: String, required: true },
avatar: String
});
これは、Mongooseには、Schema()
コンストラクターにこれらのパスを明示的に追加することなく、DocType
インターフェースに含める必要がある多くのパスをスキーマに追加する機能があるためです。たとえば、タイムスタンプやプラグインなどです。
配列
ドキュメントインターフェースで配列を定義する場合は、MongooseのTypes.Array
型またはTypes.DocumentArray
型ではなく、プレーンなJavaScript配列を使用することをお勧めします。代わりに、モデルとスキーマのTHydratedDocumentType
ジェネリックを使用して、ハイドレートされたドキュメント型にTypes.Array
型とTypes.DocumentArray
型のパスがあることを定義します。
import mongoose from 'mongoose'
const { Schema } = mongoose;
interface IOrder {
tags: Array<{ name: string }>
}
// Define a HydratedDocumentType that describes what type Mongoose should use
// for fully hydrated docs returned from `findOne()`, etc.
type OrderHydratedDocument = mongoose.HydratedDocument<
IOrder,
{ tags: mongoose.HydratedArraySubdocument<{ name: string }> }
>;
type OrderModelType = mongoose.Model<
IOrder,
{},
{},
{},
OrderHydratedDocument // THydratedDocumentType
>;
const orderSchema = new mongoose.Schema<
IOrder,
OrderModelType,
{}, // methods
{}, // query helpers
{}, // virtuals
{}, // statics
mongoose.DefaultSchemaOptions, // schema options
IOrder, // doctype
OrderHydratedDocument // THydratedDocumentType
>({
tags: [{ name: { type: String, required: true } }]
});
const OrderModel = mongoose.model<IOrder, OrderModelType>('Order', orderSchema);
// Demonstrating return types from OrderModel
const doc = new OrderModel({ tags: [{ name: 'test' }] });
doc.tags; // mongoose.Types.DocumentArray<{ name: string }>
doc.toObject().tags; // Array<{ name: string }>
async function run() {
const docFromDb = await OrderModel.findOne().orFail();
docFromDb.tags; // mongoose.Types.DocumentArray<{ name: string }>
const leanDoc = await OrderModel.findOne().orFail().lean();
leanDoc.tags; // Array<{ name: string }>
};
配列サブドキュメントの型にはHydratedArraySubdocument<RawDocType>
を、単一サブドキュメントの型にはHydratedSingleSubdocument<RawDocType>
を使用します。
スキーマメソッド、ミドルウェア、または仮想フィールドを使用していない場合は、Schema()
の最後の7つのジェネリックパラメーターを省略し、new mongoose.Schema<IOrder, OrderModelType>(...)
を使用してスキーマを定義できます。スキーマのTHydratedDocumentType
パラメーターは、主にメソッドと仮想フィールドのthis
の値を設定するためのものでです。