Skip to main content
Version: v7 - alpha

Legacy Model Definitions

caution

This API is considered legacy. It is still supported, but we strongly recommend to use the new way of defining models, as this legacy API can easily make your models complex if you need to declare an association.

There are two legacy ways of defining models:

Extending Model

import {
Sequelize,
DataTypes,
Model,
InferAttributes,
InferCreationAttributes,
CreationOptional,
} from '@sequelize/core';
import { SqliteDialect } from '@sequelize/sqlite3';

const sequelize = new Sequelize({ dialect: SqliteDialect });

class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
declare id: CreationOptional<number>;
declare firstName: string;
declare lastName: string | null;
}

User.init(
{
// Model attributes are defined here
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
lastName: {
type: DataTypes.STRING,
// allowNull defaults to true, except for primary keys
},
},
{
// Other model options go here
sequelize, // We need to pass the connection instance
},
);

// Once init has been called, the model also becomes available through sequelize.models
console.log(User === sequelize.models.User); // true

Using sequelize.define

Internally, sequelize.define calls Model.init, so both approaches are equivalent.

import { Sequelize, DataTypes } from '@sequelize/core';

const sequelize = new Sequelize({ dialect: SqliteDialect });

const User = sequelize.define(
'User',
{
// Model attributes are defined here
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
lastName: {
type: DataTypes.STRING,
// allowNull defaults to true, except for primary keys
},
},
{
// Other model options go here
},
);

// The model is also available through sequelize.models
console.log(User === sequelize.models.User); // true

With TypeScript

The case of Model.init

Model.init requires an attribute configuration for each attribute declared in typings.

Some attribute definitions don't actually need to be provided to Model.init, this is how you can make Model.init aware of this:

  • Methods used to define associations (Model.belongsTo, Model.hasMany, etc…) already handle the configuration of the necessary foreign keys attributes. Use the ForeignKey<> branded type to make Model.init aware of this:

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

    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 },
    );
  • Timestamp attributes managed by Sequelize (by default, createdAt, updatedAt, and deletedAt) don't need to be configured using Model.init, unfortunately Model.init has no way of knowing this. We recommend you use the minimum necessary configuration to suppress this type error:

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

    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 },
    );

Usage without strict types for attributes

The typings for Sequelize v5 allowed you to define models without specifying types for the attributes. This is still possible for backwards compatibility and for cases where you feel strict typing for attributes isn't worth it.

import { DataTypes, Model, Sequelize } from '@sequelize/core';
import { MySqlDialect } from '@sequelize/mysql';

const sequelize = new Sequelize({
dialect: MySqlDialect,
url: '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);
}

Usage of Sequelize#define

In Sequelize versions before v5, the default way of defining a model involved using Sequelize#define. It's still possible to define models with that, and you can also add typings to these models using interfaces.

import type {
CreationOptional,
InferAttributes,
InferCreationAttributes,
Model,
} from '@sequelize/core';
import { DataTypes, Sequelize } from '@sequelize/core';
import { MySqlDialect } from '@sequelize/mysql';

const sequelize = new Sequelize({
dialect: MySqlDialect,
url: 'mysql://root:asd123@localhost:3306/mydb',
});

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

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

const UserModel = sequelize.define<IUserModel>('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);
}

Column Options

When defining a column, apart from specifying the type of the column, and the allowNull and defaultValue options mentioned above, you can use many more options, for example:

import { Model, DataTypes, Deferrable } from '@sequelize/core';

class Foo extends Model {}
Foo.init(
{
// instantiating will automatically set the flag to true if not set
flag: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true },

// default values for dates => current time
myDate: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },

// setting allowNull to false will add NOT NULL to the column, which means an error will be
// thrown from the DB when the query is executed if the column is null. If you want to check that a value
// is not null before querying the DB, look at the validations section below.
title: { type: DataTypes.STRING, allowNull: false },

// Creating two objects with the same value will throw an error. The unique property can be either a
// boolean, or a string. If you provide the same string for multiple columns, they will form a
// composite unique key.
uniqueOne: { type: DataTypes.STRING, unique: 'compositeIndex' },
uniqueTwo: { type: DataTypes.INTEGER, unique: 'compositeIndex' },

// The unique property is simply a shorthand to create a unique constraint.
someUnique: { type: DataTypes.STRING, unique: true },

// Go on reading for further information about primary keys
identifier: { type: DataTypes.STRING, primaryKey: true },

// autoIncrement can be used to create auto_incrementing integer columns
incrementMe: { type: DataTypes.INTEGER, autoIncrement: true },

// You can specify a custom column name via the 'columnName' attribute:
fieldWithUnderscores: {
type: DataTypes.STRING,
columnName: 'field_with_underscores',
},

// It is possible to create foreign keys:
bar_id: {
type: DataTypes.INTEGER,

references: {
// This is a reference to another model
model: Bar,

// This is the column name of the referenced model
key: 'id',

// With PostgreSQL, it is optionally possible to declare when to check the foreign key constraint, passing the Deferrable type.
deferrable: Deferrable.INITIALLY_IMMEDIATE,
// Options:
// - `Deferrable.INITIALLY_IMMEDIATE` - Immediately check the foreign key constraints
// - `Deferrable.INITIALLY_DEFERRED` - Defer all foreign key constraint check to the end of a transaction
// - `Deferrable.NOT` - Don't defer the checks at all (default) - This won't allow you to dynamically change the rule in a transaction
},
},

// Comments can only be added to columns in MySQL, MariaDB, PostgreSQL and MSSQL
commentMe: {
type: DataTypes.INTEGER,
comment: 'This is a column name that has a comment',
},
},
{
sequelize,

// Using `unique: true` in an attribute above is exactly the same as creating the index in the model's options:
indexes: [{ unique: true, fields: ['someUnique'] }],
},
);