メインコンテンツにスキップ
バージョン: v6 - 安定版

TypeScript

情報

私たちは、SequelizeをTypeScriptで使いやすくするために懸命に取り組んでいます。一部の箇所はまだ開発中です。改善がリリースされるまでの間、ギャップを埋めるためにsequelize-typescriptを使用することをお勧めします。

Sequelizeは独自のTypeScript定義を提供しています。

TypeScript >= 4.1のみがサポートされていることにご注意ください。TypeScriptのサポートはSemVerに従いません。少なくとも1年間はTypeScriptのリリースをサポートしますが、その後、SemVerのMINORリリースで削除される可能性があります。

Sequelizeは実行時のプロパティ割り当てに大きく依存しているため、TypeScriptはそのままではあまり役に立ちません。モデルを機能させるためには、かなりの手動型宣言が必要です。

インストール

異なるNodeバージョンとの衝突を避けるために、Nodeの型定義は含まれていません。@types/nodeを手動でインストールする必要があります。

使用法

重要: クラスプロパティの型定義でdeclareを使用し、TypeScriptがそれらのクラスプロパティを出力しないようにする必要があります。パブリッククラスフィールドに関する注意点を参照してください。

Sequelizeモデルは、モデルの属性と作成属性がどのようなものかを定義するために、2つのジェネリック型を受け入れます。

import { Model, Optional } from 'sequelize';

// We don't recommend doing this. Read on for the new way of declaring Model typings.

type UserAttributes = {
id: number;
name: string;
// other attributes...
};

// we're telling the Model that 'id' is optional
// when creating an instance of the model (such as using Model.create()).
type UserCreationAttributes = Optional<UserAttributes, 'id'>;

class User extends Model<UserAttributes, UserCreationAttributes> {
declare id: number;
declare name: string;
// other attributes...
}

この解決策は冗長です。Sequelize >= 6.14.0は、必要なボイラープレートを大幅に削減する新しいユーティリティ型を提供しています: InferAttributesInferCreationAttributesです。これらは、モデルから属性の型定義を直接抽出します。

import { Model, InferAttributes, InferCreationAttributes, CreationOptional } from 'sequelize';

// order of InferAttributes & InferCreationAttributes is important.
class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
// 'CreationOptional' is a special type that marks the field as optional
// when creating an instance of the model (such as using Model.create()).
declare id: CreationOptional<number>;
declare name: string;
// other attributes...
}

InferAttributesInferCreationAttributesの動作について知っておくべき重要なこと: これらは、クラスのすべての宣言されたプロパティを選択しますが、以下を除きます。

  • 静的フィールドとメソッド。
  • メソッド (型が関数であるもの)。
  • 型がブランド型NonAttributeを使用しているもの。
  • 次のようにInferAttributesを使用することで除外されたもの: InferAttributes<User, { omit: 'properties' | 'to' | 'omit' }>
  • モデルのスーパークラス (ただし、中間クラスではない!) によって宣言されたもの。属性の1つがModelのプロパティの1つと同じ名前を共有する場合は、その名前を変更してください。これを行うと、いずれにしても問題が発生する可能性があります。
  • ゲッターとセッターは自動的に除外されません。それらの戻り値/パラメーターの型をNonAttributeに設定するか、omitに追加して除外してください。

InferCreationAttributesInferAttributesと同じように機能しますが、1つの例外があります。:プロパティCreationOptional型を使用して型付けされたものはオプションとしてマークされます。nullまたはundefinedを受け入れる属性は、CreationOptionalを使用する必要がないことに注意してください。

class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
declare firstName: string;

// there is no need to use CreationOptional on lastName because nullable attributes
// are always optional in User.create()
declare lastName: string | null;
}

// ...

await User.create({
firstName: 'Zoé',
// last name omitted, but this is still valid!
});

クラスインスタンスフィールドまたはゲッターでCreationOptionalNonAttributeを使用するだけで済みます。

属性の厳密な型チェックを行う最小限のTypeScriptプロジェクトの例

/**
* Keep this file in sync with the code in the "Usage" section
* in /docs/manual/other-topics/typescript.md
*
* Don't include this comment in the md file.
*/
import {
Association, DataTypes, HasManyAddAssociationMixin, HasManyCountAssociationsMixin,
HasManyCreateAssociationMixin, HasManyGetAssociationsMixin, HasManyHasAssociationMixin,
HasManySetAssociationsMixin, HasManyAddAssociationsMixin, HasManyHasAssociationsMixin,
HasManyRemoveAssociationMixin, HasManyRemoveAssociationsMixin, Model, ModelDefined, Optional,
Sequelize, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ForeignKey,
} from 'sequelize';

const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb');

// 'projects' is excluded as it's not an attribute, it's an association.
class User extends Model<InferAttributes<User, { omit: 'projects' }>, InferCreationAttributes<User, { omit: 'projects' }>> {
// id can be undefined during creation when using `autoIncrement`
declare id: CreationOptional<number>;
declare name: string;
declare preferredName: string | null; // for nullable fields

// timestamps!
// createdAt can be undefined during creation
declare createdAt: CreationOptional<Date>;
// updatedAt can be undefined during creation
declare updatedAt: CreationOptional<Date>;

// Since TS cannot determine model association at compile time
// we have to declare them here purely virtually
// these will not exist until `Model.init` was called.
declare getProjects: HasManyGetAssociationsMixin<Project>; // Note the null assertions!
declare addProject: HasManyAddAssociationMixin<Project, number>;
declare addProjects: HasManyAddAssociationsMixin<Project, number>;
declare setProjects: HasManySetAssociationsMixin<Project, number>;
declare removeProject: HasManyRemoveAssociationMixin<Project, number>;
declare removeProjects: HasManyRemoveAssociationsMixin<Project, number>;
declare hasProject: HasManyHasAssociationMixin<Project, number>;
declare hasProjects: HasManyHasAssociationsMixin<Project, number>;
declare countProjects: HasManyCountAssociationsMixin;
declare createProject: HasManyCreateAssociationMixin<Project, 'ownerId'>;

// You can also pre-declare possible inclusions, these will only be populated if you
// actively include a relation.
declare projects?: NonAttribute<Project[]>; // Note this is optional since it's only populated when explicitly requested in code

// getters that are not attributes should be tagged using NonAttribute
// to remove them from the model's Attribute Typings.
get fullName(): NonAttribute<string> {
return this.name;
}

declare static associations: {
projects: Association<User, Project>;
};
}

class Project extends Model<
InferAttributes<Project>,
InferCreationAttributes<Project>
> {
// id can be undefined during creation when using `autoIncrement`
declare id: CreationOptional<number>;

// foreign keys are automatically added by associations methods (like Project.belongsTo)
// by branding them using the `ForeignKey` type, `Project.init` will know it does not need to
// display an error if ownerId is missing.
declare ownerId: ForeignKey<User['id']>;
declare name: string;

// `owner` is an eagerly-loaded association.
// We tag it as `NonAttribute`
declare owner?: NonAttribute<User>;

// createdAt can be undefined during creation
declare createdAt: CreationOptional<Date>;
// updatedAt can be undefined during creation
declare updatedAt: CreationOptional<Date>;
}

class Address extends Model<
InferAttributes<Address>,
InferCreationAttributes<Address>
> {
declare userId: ForeignKey<User['id']>;
declare address: string;

// createdAt can be undefined during creation
declare createdAt: CreationOptional<Date>;
// updatedAt can be undefined during creation
declare updatedAt: CreationOptional<Date>;
}

Project.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true
},
name: {
type: new DataTypes.STRING(128),
allowNull: false
},
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
},
{
sequelize,
tableName: 'projects'
}
);

User.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true
},
name: {
type: new DataTypes.STRING(128),
allowNull: false
},
preferredName: {
type: new DataTypes.STRING(128),
allowNull: true
},
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
},
{
tableName: 'users',
sequelize // passing the `sequelize` instance is required
}
);

Address.init(
{
address: {
type: new DataTypes.STRING(128),
allowNull: false
},
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
},
{
tableName: 'address',
sequelize // passing the `sequelize` instance is required
}
);

// You can also define modules in a functional way
interface NoteAttributes {
id: number;
title: string;
content: string;
}

// You can also set multiple attributes optional at once
type NoteCreationAttributes = Optional<NoteAttributes, 'id' | 'title'>;

// And with a functional approach defining a module looks like this
const Note: ModelDefined<
NoteAttributes,
NoteCreationAttributes
> = sequelize.define(
'Note',
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true
},
title: {
type: new DataTypes.STRING(64),
defaultValue: 'Unnamed Note'
},
content: {
type: new DataTypes.STRING(4096),
allowNull: false
}
},
{
tableName: 'notes'
}
);

// Here we associate which actually populates out pre-declared `association` static and other methods.
User.hasMany(Project, {
sourceKey: 'id',
foreignKey: 'ownerId',
as: 'projects' // this determines the name in `associations`!
});

Address.belongsTo(User, { targetKey: 'id' });
User.hasOne(Address, { sourceKey: 'id' });

async function doStuffWithUser() {
const newUser = await User.create({
name: 'Johnny',
preferredName: 'John',
});
console.log(newUser.id, newUser.name, newUser.preferredName);

const project = await newUser.createProject({
name: 'first!'
});

const ourUser = await User.findByPk(1, {
include: [User.associations.projects],
rejectOnEmpty: true // Specifying true here removes `null` from the return type!
});

// Note the `!` null assertion since TS can't know if we included
// the model or not
console.log(ourUser.projects![0].name);
}

(async () => {
await sequelize.sync();
await doStuffWithUser();
})();

Model.initの場合

Model.initには、型定義で宣言された各属性の属性構成が必要です。

一部の属性は実際にはModel.initに渡す必要はありません。この静的メソッドにそれらを認識させる方法は次のとおりです。

  • 関連付けを定義するために使用されるメソッド (Model.belongsToModel.hasManyなど) は、必要な外部キー属性の構成をすでに処理しています。Model.initを使用してこれらの外部キーを構成する必要はありません。ForeignKey<>ブランド型を使用して、外部キーを構成する必要がないことをModel.initに認識させます。

    import {
    Model,
    InferAttributes,
    InferCreationAttributes,
    DataTypes,
    ForeignKey,
    } from 'sequelize';

    class Project extends Model<InferAttributes<Project>, InferCreationAttributes<Project>> {
    id: number;
    userId: ForeignKey<number>;
    }

    // this configures the `userId` attribute.
    Project.belongsTo(User);

    // therefore, `userId` doesn't need to be specified here.
    Project.init(
    {
    id: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true,
    },
    },
    { sequelize },
    );
  • Sequelizeによって管理されるタイムスタンプ属性 (デフォルトでは、createdAtupdatedAt、およびdeletedAt) は、Model.initを使用して構成する必要はありません。残念ながら、Model.initにはこれを認識する方法がありません。このエラーを抑制するために必要な最小限の構成を使用することをお勧めします。

    import { Model, InferAttributes, InferCreationAttributes, DataTypes } from 'sequelize';

    class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
    id: number;
    createdAt: Date;
    updatedAt: Date;
    }

    User.init(
    {
    id: {
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true,
    },
    // technically, `createdAt` & `updatedAt` are added by Sequelize and don't need to be configured in Model.init
    // but the typings of Model.init do not know this. Add the following to mute the typing error:
    createdAt: DataTypes.DATE,
    updatedAt: DataTypes.DATE,
    },
    { sequelize },
    );

属性の厳密な型がない場合の使用法

Sequelize v5の型定義では、属性の型を指定せずにモデルを定義できました。これは下位互換性のため、および属性の厳密な型付けがそれだけの価値がないと感じる場合に引き続き可能です。

/**
* Keep this file in sync with the code in the "Usage without strict types for
* attributes" section in /docs/manual/other-topics/typescript.md
*
* Don't include this comment in the md file.
*/
import { Sequelize, Model, DataTypes } from 'sequelize';

const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb');

class User extends Model {
declare id: number;
declare name: string;
declare preferredName: string | null;
}

User.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
name: {
type: new DataTypes.STRING(128),
allowNull: false,
},
preferredName: {
type: new DataTypes.STRING(128),
allowNull: true,
},
},
{
tableName: 'users',
sequelize, // passing the `sequelize` instance is required
},
);

async function doStuffWithUserModel() {
const newUser = await User.create({
name: 'Johnny',
preferredName: 'John',
});
console.log(newUser.id, newUser.name, newUser.preferredName);

const foundUser = await User.findOne({ where: { name: 'Johnny' } });
if (foundUser === null) return;
console.log(foundUser.name);
}

Sequelize#defineの使用

v5より前のSequelizeバージョンでは、モデルを定義するデフォルトの方法はSequelize#defineを使用することでした。それを使用してモデルを定義することもできます。また、インターフェースを使用してこれらのモデルに型定義を追加することもできます。

/**
* Keep this file in sync with the code in the "Usage of `sequelize.define`"
* section in /docs/manual/other-topics/typescript.md
*
* Don't include this comment in the md file.
*/
import { Sequelize, Model, DataTypes, CreationOptional, InferAttributes, InferCreationAttributes } from 'sequelize';

const sequelize = new Sequelize('mysql://root:asd123@localhost:3306/mydb');

// We recommend you declare an interface for the attributes, for stricter typechecking

interface UserModel extends Model<InferAttributes<UserModel>, InferCreationAttributes<UserModel>> {
// Some fields are optional when calling UserModel.create() or UserModel.build()
id: CreationOptional<number>;
name: string;
}

const UserModel = sequelize.define<UserModel>('User', {
id: {
primaryKey: true,
type: DataTypes.INTEGER.UNSIGNED,
},
name: {
type: DataTypes.STRING,
},
});

async function doStuff() {
const instance = await UserModel.findByPk(1, {
rejectOnEmpty: true,
});

console.log(instance.id);
}

ユーティリティ型

モデルクラスのリクエスト

ModelStaticは、モデルクラスの型を定義するために使用するように設計されています。

モデルクラスをリクエストし、そのクラスで定義された主キーのリストを返すユーティリティメソッドの例を次に示します。

import {
ModelStatic,
ModelAttributeColumnOptions,
Model,
InferAttributes,
InferCreationAttributes,
CreationOptional,
} from 'sequelize';

/**
* Returns the list of attributes that are part of the model's primary key.
*/
export function getPrimaryKeyAttributes(model: ModelStatic<any>): ModelAttributeColumnOptions[] {
const attributes: ModelAttributeColumnOptions[] = [];

for (const attribute of Object.values(model.rawAttributes)) {
if (attribute.primaryKey) {
attributes.push(attribute);
}
}

return attributes;
}

class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
id: CreationOptional<number>;
}

User.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
},
{ sequelize },
);

const primaryAttributes = getPrimaryKeyAttributes(User);

モデルの属性の取得

特定のモデルの属性のリストにアクセスする必要がある場合は、Attributes<Model>CreationAttributes<Model>を使用する必要があります。

これらは、パラメーターとして渡されたモデルの属性 (および作成属性) を返します。

InferAttributesInferCreationAttributesと混同しないでください。これら2つのユーティリティ型は、モデルのパブリッククラスフィールドから属性のリストを自動的に作成するために、モデルの定義でのみ使用する必要があります。これらはクラスベースのモデル定義 (Model.initを使用する場合) でのみ機能します。

Attributes<Model>CreationAttributes<Model>は、作成方法 (Model.initまたはSequelize#defineであるかどうか) に関係なく、任意のモデルの属性リストを返します。

モデルクラスと属性の名前をリクエストし、対応する属性メタデータを返すユーティリティ関数の例を次に示します。

import {
ModelStatic,
ModelAttributeColumnOptions,
Model,
InferAttributes,
InferCreationAttributes,
CreationOptional,
Attributes,
} from 'sequelize';

export function getAttributeMetadata<M extends Model>(
model: ModelStatic<M>,
attributeName: keyof Attributes<M>,
): ModelAttributeColumnOptions {
const attribute = model.rawAttributes[attributeName];
if (attribute == null) {
throw new Error(`Attribute ${attributeName} does not exist on model ${model.name}`);
}

return attribute;
}

class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
id: CreationOptional<number>;
}

User.init(
{
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true,
},
},
{ sequelize },
);

const idAttributeMeta = getAttributeMetadata(User, 'id'); // works!

// @ts-expect-error
const nameAttributeMeta = getAttributeMetadata(User, 'name'); // fails because 'name' is not an attribute of User