Supercharge Your Node.js App: Seamlessly Set Up Sequelize with TypeScript and sequelize-auto-migrations-v2 for auto migrations
Stephen Akugbe
Posted on August 2, 2023
When I initially started using Typescript, I wanted to automatically generate migrations as it was tiring to create models and also create migrations.
To follow this tutorial, you must be familiar with:
- Sequelize
- NodeJS
- Typescript
- Initializing sequelize in a NodeJS application, you can checkout the sequelize official docs for that.
I had used sequelize-auto-migrations-v2 in the past and I knew I just had to find a way to make it work with TypeScript and NodeJS.
So, let me walk you through, how I achieved this... N
ot to worry, I also added the github repo for this at the end of this write up.
First, I created the types for sequelize which I named SequelizeTypes.ts
import { BuildOptions, Op } from "sequelize";
import { DataTypeAbstract, ModelAttributeColumnOptions } from "sequelize";
import { Model } from "sequelize";
type SequelizeAttribute = string | DataTypeAbstract | ModelAttributeColumnOptions;
export type SequelizeAttributes<T extends { [key: string]: any }> = {
};
export type SequelizeModel = typeof Model & {
new (values?: object, options?: BuildOptions): Model;
};
export type ModelInstance = Model<any, any>;
/**
* For building model instance used in creating model in the model definition
*/
export type ModelStatic<T> = typeof Model & {
new (values?: object, options?: BuildOptions): T;
associate: (models: any) => any;
setDataValue: (key: any, val: any) => void;
};
interface ISequelizeAssociatable<T> extends Model<any, any> {
associate(reg: T): void;
}
export function isAssociatable<T>(model: {
associate?: Function;
}): model is ISequelizeAssociatable<T> {
return typeof model.associate === "function";
}
Now, let me breakdown this file in the best possible way:
'SequelizeAttribute' represents different attribute options that can be used to define Sequelize models.
'SequelizeAttributes' is a mapped type that takes a generic type 'T' representing a model definition and transforms it into an object where each key is a property name of 'T', and each value is of type 'SequelizeAttribute'.
'SequelizeModel' type is a custom type that extends typeof Model, which represents the constructor of Sequelize models. The SequelizeModel type allows you to create instances of a Sequelize model using the 'new' keyword.
'ModelInstance' is a type alias representing a Sequelize model instance.
'ModelStatic' is a custom type that extends typeof Model, which represents a Sequelize model definition. The ModelStatic type provides additional properties for defining custom associations (associate method) and setting data values.
'ISequelizeAssociatable' is an interface that extends the 'Model interface provided by sequelize and it includes the 'associate' method which is used to define associations between Sequelize models.
Overall, this is the basis of the model files that I'll be creating.
Now, here's a simple User model I created:
import { Sequelize } from "sequelize";
import { SequelizeAttributes, ModelStatic } from "../types/SequelizeTypes";
import { Model, Optional, DataTypes } from "sequelize";
import { ModelRegistry } from ".";
import { v4 as uuidv4 } from "uuid";
export interface UserAttributes {
id: string;
firstName?: string;
lastName?: string;
email: string;
phoneNumber?: number;
password: string;
dob?: Date;
status: string;
token?: string;
tokenExpire?: Date;
createdAt?: Date;
updatedAt?: Date;
deletedAt?: Date;
}
interface UserCreationAttributes extends Optional<UserAttributes, "id" | "email" | "password"> {}
export interface UserInstance
extends Model<UserAttributes, UserCreationAttributes>,
UserAttributes {}
//--> Model attributes
export const UserModelAttributes: SequelizeAttributes<UserAttributes> = {
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: () => uuidv4(),
},
firstName: {
type: DataTypes.STRING,
},
lastName: {
type: DataTypes.STRING,
},
email: {
type: DataTypes.STRING,
unique: true,
},
phoneNumber: {
type: DataTypes.STRING,
allowNull: true,
unique: true,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
dob: {
type: DataTypes.DATE,
allowNull: true,
},
status: {
type: DataTypes.ENUM,
allowNull: false,
values: ["active", "inactive"],
defaultValue: "active",
},
token: DataTypes.STRING,
tokenExpire: DataTypes.DATE,
};
// --> Factory....
export function UserFactory(sequelize: Sequelize) {
const User = <ModelStatic<UserInstance>>sequelize.define("User", UserModelAttributes, {
tableName: "users",
defaultScope: {
attributes: {
exclude: ["password", "deletedAt"],
},
order: [["createdAt", "DESC"]],
},
freezeTableName: true,
timestamps: true,
paranoid: true,
});
User.associate = function (models: ModelRegistry) {
const { User } = models;
//Model association can be created here
};
User.prototype.toJSON = function () {
const values = { ...this.get() };
delete values.password;
return values;
};
return User;
}
then there's an index file which bundles and exports all created models:
"use strict";
import { QueryTypes, Sequelize } from "sequelize";
import config from "../config/config";
import AppConfig from "../config/AppConfig";
import { UserFactory } from "./User";
import { ModelStatic, isAssociatable } from "../types/SequelizeTypes";
// @ts-ignore
const database = config[AppConfig.NODE_ENV] || config.development;
const sequelize = new Sequelize(database.database, database.username, database.password, {
...database,
logging: false,
dialect: database.dialect,
});
export const User = UserFactory(sequelize);
const models = {
User,
};
export type ModelRegistry = typeof models;
export type ModelRegistryKeys = keyof typeof models;
Object.values(models).forEach((model: ModelStatic<any>) => {
if (isAssociatable<ModelRegistry>(model)) {
model.associate(models);
}
});
(async () => {
// await sequelize.sync({ force: true });
})();
export default sequelize;
There are other intricacies to this project which are not explained here, however, you can access the full project on github
Posted on August 2, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.