Back to Sequelize

Legacy Model Definitions

docs/other-topics/legacy-model-definitions.mdx

latest9.6 KB
Original Source

import CodeBlock from '@theme/CodeBlock'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

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

<Tabs groupId="ts-js"> <TabItem value="ts" label="TypeScript">
typescript
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
</TabItem> <TabItem value="js" label="JavaScript (CJS)">
javascript
import { Sequelize, DataTypes, Model } from '@sequelize/core';
import { SqliteDialect } from '@sequelize/sqlite3';

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

class User extends Model {}

User.init(
  {
    // 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
    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
</TabItem> </Tabs>

Using sequelize.define

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

js
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:

    typescript
    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:

    typescript
    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 modelInitNoAttributesExample from '!!raw-loader!@site/.sequelize/v7/packages/core/test/types/typescript-docs/model-init-no-attributes.ts';

<CodeBlock className="language-typescript">{modelInitNoAttributesExample}</CodeBlock>

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 defineExample from '!!raw-loader!@site/.sequelize/v7/packages/core/test/types/typescript-docs/define.ts';

<CodeBlock className="language-typescript">{defineExample}</CodeBlock>

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:

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