20

I already have my server application written in Node, PostgreSQL, Sequelize using Fastify.

Now I would like to use TypeScript. Can anyone tell me how to begin rewriting my Server application using TypeScript.

Rafael
  • 499
  • 1
  • 3
  • 12
  • 1
    Hey this was the best solution I found: http://rousseau-alexandre.fr/en/programming/2019/06/19/express-typescript.html Hope it helps – onzinsky Jun 10 '20 at 00:03
  • Sequelize has a document for using Typescript. https://sequelize.org/docs/v6/other-topics/typescript/ – Ali Hesari Nov 14 '22 at 14:28

3 Answers3

31

Updated

/**
 * 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();
})();

OLD

Using Decorators is something you should avoid as much as possible, they are not ECMAScript standard. They are even consider legacy. It is why I'm going to show you how to use sequelize with typescript.

we just need to follow the docs: https://sequelize.org/v5/manual/typescript.html but as it is not very clear, or at least to me. It took me a while understand it.

There it says that you need to install this tree things

 * @types/node
 * @types/validator // this one is not need it
 * @types/bluebird

npm i -D @types/node @types/bluebird

then let's assume your project looks like so:

myProject
--src
----models
------index.ts
------user-model.ts
------other-model.ts
----controllers
----index.ts
--package.json

Let's create the user model first

`./src/models/user-model.ts`
import { BuildOptions, DataTypes, Model, Sequelize } from "sequelize";

export interface UserAttributes {
    id: number;
    name: string;
    email: string;
    createdAt?: Date;
    updatedAt?: Date;
}
export interface UserModel extends Model<UserAttributes>, UserAttributes {}
export class User extends Model<UserModel, UserAttributes> {}

export type UserStatic = typeof Model & {
    new (values?: object, options?: BuildOptions): UserModel;
};

export function UserFactory (sequelize: Sequelize): UserStatic {
    return <UserStatic>sequelize.define("users", {
        id: {
            type: DataTypes.INTEGER,
            autoIncrement: true,
            primaryKey: true,
        },
        email: {
            type: DataTypes.STRING,
            allowNull: false,
            unique: true,
        },
        name: {
            type: DataTypes.STRING,
            allowNull: false,
        },
        createdAt: {
            type: DataTypes.DATE,
            allowNull: false,
            defaultValue: DataTypes.NOW,
        },
        updatedAt: {
            type: DataTypes.DATE,
            allowNull: false,
            defaultValue: DataTypes.NOW,
        },
    });
}

Now just to play arrow let's create another-model.ts

`./src/models/another-model.ts`

import { BuildOptions, DataTypes, Model, Sequelize } from "sequelize";

export interface SkillsAttributes {
    id: number;
    skill: string;
    createdAt?: Date;
    updatedAt?: Date;
}
export interface SkillsModel extends Model<SkillsAttributes>, SkillsAttributes {}
export class Skills extends Model<SkillsModel, SkillsAttributes> {}

export type SkillsStatic = typeof Model & {
    new (values?: object, options?: BuildOptions): SkillsModel;
};

export function SkillsFactory (sequelize: Sequelize): SkillsStatic {
    return <SkillsStatic>sequelize.define("skills", {
        id: {
            type: DataTypes.INTEGER,
            autoIncrement: true,
            primaryKey: true,
        },
        skill: {
            type: DataTypes.STRING,
            allowNull: false,
            unique: true,
        },
        createdAt: {
            type: DataTypes.DATE,
            allowNull: false,
            defaultValue: DataTypes.NOW,
        },
        updatedAt: {
            type: DataTypes.DATE,
            allowNull: false,
            defaultValue: DataTypes.NOW,
        },
    });
}

Our Entities are done. now the db connection.

open ./src/models/index.ts there is where we gonna place the seqelize instance

`./src/models/index.ts`

import * as sequelize from "sequelize";
import {userFactory} from "./user-model";
import {skillsFactory} from "./other-model";

export const dbConfig = new sequelize.Sequelize(
    (process.env.DB_NAME = "db-name"),
    (process.env.DB_USER = "db-user"),
    (process.env.DB_PASSWORD = "db-password"),
    {
        port: Number(process.env.DB_PORT) || 54320,
        host: process.env.DB_HOST || "localhost",
        dialect: "postgres",
        pool: {
            min: 0,
            max: 5,
            acquire: 30000,
            idle: 10000,
        },
    }
);

// SOMETHING VERY IMPORTANT them Factory functions expect a
// sequelize instance as parameter give them `dbConfig`

export const User = userFactory(dbConfig);
export const Skills = skillsFactory(dbConfig);

// Users have skills then lets create that relationship

User.hasMay(Skills);

// or instead of that, maybe many users have many skills
Skills.belongsToMany(Users, { through: "users_have_skills" });

// the skill is the limit!

on our index.ts add, if you just want to open connection

  db.sequelize
        .authenticate()
        .then(() => logger.info("connected to db"))
        .catch(() => {
            throw "error";
        });

or if you want to create them tables

  db.sequelize
        .sync()
        .then(() => logger.info("connected to db"))
        .catch(() => {
            throw "error";
        });

some like this

 
import * as bodyParser from "body-parser";
import * as express from "express";
import { dbConfig } from "./models";
import { routes } from "./routes";
import { logger } from "./utils/logger";
import { timeMiddleware } from "./utils/middlewares";

export function expressApp () {
    dbConfig
        .authenticate()
        .then(() => logger.info("connected to db"))
        .catch(() => {
            throw "error";
        });

    const app: Application = express();
    if (process.env.NODE_ENV === "production") {
        app.use(require("helmet")());
        app.use(require("compression")());
    } else {
        app.use(require("cors")());
    }

    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true, limit: "5m" }));
    app.use(timeMiddleware);
    app.use("/", routes(db));

    return app;
}

Once again the sky is the limit. If you do this you'll have all the power of the autocomplete. here an example: https://github.com/EnetoJara/resume-app

Ernesto
  • 3,944
  • 1
  • 14
  • 29
  • How to implement this for create works? When i do MyModel.create () he expect an object but not exactly the properties from model – Jordi Castillo Dec 02 '20 at 10:44
  • 1
    Hola, como estas ? you can take a look at this medium post https://medium.com/@enetoOlveda/use-sequelize-and-typescript-like-a-pro-with-out-the-legacy-decorators-fbaabed09472 – Ernesto Dec 02 '20 at 21:13
  • 5
    Many TypeScript features aren't part of the ECMAScript standard, that doesn't mean they should be avoided. And decorators are most definitely not legacy, they're a tc39 stage 2 proposal yet to be finalized. – cuzox Jan 07 '21 at 22:40
  • Whatever floats your boat – Ernesto Jan 08 '21 at 02:51
  • Whether the model compatible with typescript can build from the `sequelize-cli`? as we do like `sequelize-cli model:generate command`? – KTM Jan 14 '21 at 16:29
  • sorry I didn't get that. I am a ESL person that's why my ugly english xDD what you do mean by that ? – Ernesto Jan 15 '21 at 03:54
  • @Ernesto What does `export class User extends Model {}` even do? I don't see how it could be used. Even the return type of `create()` or `findOne()` are `UserModel` interface. The `class User` looks redundant to me. – Naveen Attri Jan 29 '21 at 18:03
  • Is there anything for version 6? I attempted to use the example on the sequelize website as a guide and not having much luck – thxmike Mar 17 '21 at 13:43
  • this example is for v5, but let me take a look at v6 – Ernesto Mar 19 '21 at 01:38
  • if you use the `sequelize.define` form its the same thing here you can find an example I wrote https://github.com/EnetoJara/resume-app – Ernesto Mar 19 '21 at 01:42
  • I am getting ```@Table annotation is missing on class "User"``` When I tried this approach. Any ideas how I can fix this ? I use NestJs FYI – GRS Feb 16 '22 at 19:20
  • This question was originally asking on how to use decorators that's why I started with `Using Decorators is something you should avoid as much as possible` If you have NestJS this post wont help you, NestJS comes with its own way of implementation as it is a framework – Ernesto Feb 16 '22 at 20:13
9

Use sequelize-typescript. Convert your tables and views into a class that extends Model object.

Use annotations in classes for defining your table.

import {Table, Column, Model, HasMany} from 'sequelize-typescript';
 
@Table
class Person extends Model<Person> {
 
  @Column
  name: string;
 
  @Column
  birthday: Date;
 
  @HasMany(() => Hobby)
  hobbies: Hobby[];
}

Create a connection to DB by creating the object:

const sequelize = new Sequelize(configuration...). 

Then register your tables to this object.

sequelize.add([Person])

For further reference check this module. Sequelize-Typescript

Naor Levi
  • 1,713
  • 1
  • 13
  • 27
0

I suggest shortening your typing and doing something like this:

interface UserAttributes extends Model {
    id: number;
    name: string;
    email: string;
    createdAt?: Date;
    updatedAt?: Date;
}