Sequelize + TypeScript - What you need to know
Tags: typescript, sequelize, refactoring, orm
Published: 2021-03-08 05:38:10

---
title: Sequelize + TypeScript - What you need to know
published: true
description: Tips and tricks on how to setup Sequelize with TypeScript.
tags: #typescript, #sequelize, #refactoring, #orm
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/svk9h5brc9jo5369n4ol.png
---
Hi Dev community 👋🏾!


I just decided that I want to write a post so here I AM. I didn't know what to write about and I landed on the idea of _Sequelize + TypeScript_ my experience working with Sequelize and adding TypeScript to it.

## Table of Content
  * [A Little bit of Background](#chapter-1)
  * [Setup Sequelize](#chapter-2)
  * [Setup Sequelize Models](#chapter-3)
  * [Adding Model Association](#chapter-4)
  * [Conclusion](#chapter-5)

### A Little bit of Background <a name="chapter-1"></a>

So the idea came from a project that I was managing and the client decided that they wanted the project handed in yesterday (as usual), so the company put together a small group of Jr. Engineers plus me as the Senior/Lead Engineer so I would be in charge of choosing the stack so I took the decision of using:
    
  - Express/NodeJS (Backend)
  - PostgreSQL (Database)
  - Plain React (Frontend)
  - Sequelize (ORM)

Since any of the Jr. Engineers had any experience developing on TypeScript nor working on a microservices-based architecture I decided that it would be good for us to use plained JS and monolithic architecture since that what they knew at the moment and I didn't want to slow down the project.


Now I hear you asking _what does this have to do with Sequelize + TypeScript?_ Well, everything. The company decided to use the project as a base ground for another project and it's here when we noticed all the limitations and problems that we have with the current setup. Just to name a few:

1. A lot of duplicated code both in the Backend and the Frontend
2. Slow dev environment
3. Weird bugs that cause the project to crash
4. It was hard to understand what is doing what
5. Database Models were messy
6. _And more..._

So here I'm refactoring the project using TypeScript and setting up a Microservice-based architecture with Event-Based Communication (as it should) while my team is working on bug fixes and waiting for me to be done with the refactoring.

Well enough talking let's dive in and set up Sequelize to use TypeScript.


### Setup Sequelize <a name="chapter-2"></a>

I'll be using the [sequelize-cli](https://www.npmjs.com/package/sequelize-cli) tool which generates the Sequelize folder structure and configuration based on the `.sequelizerc` file which should look like this

```JS
const path = require('path');

module.exports = {
  config: path.resolve('.', 'config.js'),
  'models-path': path.resolve('./db/models'),
  'seeders-path': path.resolve('./db/seeders'),
  'migrations-path': path.resolve('./db/migrations')
};
```

You can now run `npx sequelize-cli init` and you will have the following project structure

```
- config.js
- db/
  - migrations/
  - models/
    - index.js
  - seeders/
```

Your `db/models/index.js` file will look like this

```JS
'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');

const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require('../../app/config')[env];
const db = {};

let sequelize;
if (config.url) {
  sequelize = new Sequelize(config.url, config);
} else {
  // another sequelize configuration
}

fs.readdirSync(__dirname)
  .filter(file => {
    return (
      file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js'
    );
  })
  .forEach(file => {
    const model = sequelize['import'](path.join(__dirname, file));
    db[model.name] = model;
  });

Object.keys(db).forEach(modelName => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;
```

Yeah, a bunch of code, but this essentially eases the use of models and reduces the amount of `requires/imports` that you'd do in your models in the future.

_Pretty cool stuff, but I want to use TypeScript_ I hear you say. Ok so let's change our first file to TypeScript. Rename the `index.js` file to `index.ts` and let's change some things in it

```TS
import { Sequelize } from 'sequelize';

const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../../config.js')[env];

const  sequelize = config.url
  ? new Sequelize(config.url, config)
  : new Sequelize(config.database, config.username, config.password, config);

export { Sequelize, sequelize };

```

Yup, that's it!

Ok, let me explain why we removed a bunch of code from the file, and the reason is due to _TypeScript being TypeScript_. I just didn't want to type the `db` Object that is being generated based on the files inside the `models/` directory. So we removed all of that and just exported `Sequelize` (Class) and `sequelize` (instance). The `sequelize` instance has a reference to the database connection so when we create our models we use this instance so we can communicate with the database through the models.

The first file is done and a bunch more to go.

### Setup Sequelize Models <a name="chapter-3"></a>

If we were to use JS a Sequelize model would look like this. 

**NOTE:** I'll be declaring two example models: Author, and Book.

Let's first see how to add a Sequelize Model using JS. The Author Model would look like the following

```JS
// /models/author.js
module.exports = (sequelize, DataTypes) => {
  const Author = sequelize.define(
    'Author',
    {
      id: {
        allowNull: false,
        autoIncrement: false,
        primaryKey: true,
        type: DataTypes.UUID,
        unique: true,
      },
      firstName: {
        allowNull: true,
        type: DataTypes.TEXT,
      },
      lastName: {
        allowNull: false,
        type: DataTypes.TEXT,
      },
      email: {
        allowNull: true,
        type: DataTypes.TEXT,
      },
    },
    {}
  );

  return Author;
};
```

So let's give the TypeScript treat to the Author model as well. Change the name to `author.ts` and let's define some interfaces

```TS
import { Model, Optional } from 'sequelize';

interface AuthorAttributes {
  id: string;
  firstName: string;
  lastName: string;
  email: string;
};

/*
  We have to declare the AuthorCreationAttributes to
  tell Sequelize and TypeScript that the property id,
  in this case, is optional to be passed at creation time
*/
interface AuthorCreationAttributes
  extends Optional<AuthorAttributes, 'id'> {}

interface AuthorInstance
  extends Model<AuthorAttributes, AuthorCreationAttributes>,
    AuthorAttributes {
      createdAt?: Date;
      updatedAt?: Date;
    }
```

We need these interfaces to tell TypeScript which properties an author instance has.

Now let's define the Author Model in the TS way

```TS
// model/author.ts
import { sequelize } from '.';

// ... instances code

const Author = sequelize.define<AuthorInstance>(
  'Author',
  {
    id: {
      allowNull: false,
      autoIncrement: false,
      primaryKey: true,
      type: DataTypes.UUID,
      unique: true,
    },
    firstName: {
      allowNull: true,
      type: DataTypes.TEXT,
    },
    lastName: {
      allowNull: false,
      type: DataTypes.TEXT,
    },
    email: {
      allowNull: true,
      type: DataTypes.TEXT,
    },
  }
);
```

Now you should be wondering, _why do I have to go through all of that if I'll end up doing the same?_ Well, if you made a typo on any property of the model define object, TypeScript will yell at you and tell you that the property `X` is missing, same if you try to add a property that is not defined on the `AuthorAttributes` interface TypeScript will tell you that property `Y` is not defined on type `AuthorAttributes`

Now that we know how to declare a Sequelize model using TypeScript let's define the `book.ts` model.

```TS
// /models/book.ts
import { Model, Optional } from 'sequelize';
import { sequelize } from '.';

interface BookAttributes {
  id: string;
  title: string;
  numberOfPages: number;
  authorId: string;
}

interface BookCreationAttributes
  extends Optional<BookAttributes, 'id'> {}

interface BookInstance
  extends Model<BookAttributes, BookCreationAttributes>,
    BookAttributes {
      createdAt?: Date;
      updatedAt?: Date;
    }

const Book = sequelize.define<BookInstance>(
  'Book',
  {
    id: {
      allowNull: false,
      autoIncrement: false,
      primaryKey: true,
      type: DataTypes.UUID,
      unique: true,
    },
    title: {
      allowNull: true,
      type: DataTypes.TEXT,
    },
    numberOfPages: {
      allowNull: false,
      type: DataTypes.INTEGER,
    },
    authorId: {
      allowNull: true,
      type: DataTypes.UUID,
    },
  }
);

export default Book;
```

That's it! That's how you define a Sequelize Model using TypeScript.

### Adding Model Association <a name="chapter-4"></a>

If you want to add associations to the model let's say `An Author HAS MANY Books` then in JS you'd do something like this

```JS
// model/author.js
  Author.associate = models => {
    Author.hasMany(models.Book, {
      foreignKey: 'authorId',
    });
  };
```

```JS
// model/book.js
  Book.associate = models => {
    Book.belongsTo(models.Author, {
      foreignKey: 'id'
    });
  };
```

So do you remember when I said that the code generated when we ran `npx sequelize-cli init` will help us when trying to do `requires/imports`? That time has come! 

Sequelize will try to run the associate function if defined and will add the defined associations to the Models.

But we are here for TypeScript not for JS, so how do we do this in our defined models in TypeScript? Let's see how.

```TS
// models/author.ts
import Book from './book';

// ... Code Defining the Author Model

Author.hasMany(Book, {
  /*
    You can omit the sourceKey property
    since by default sequelize will use the primary key defined
    in the model - But I like to be explicit 
  */
  sourceKey: 'id',
  foreignKey: 'authorId',
  as: 'books'
});
```

```TS
// models/book.ts
import Author from './author';

// ... Code Defining the Book Model

Book.belongsTo(Author, {
  foreignKey: 'authorId',
  as: 'author'
});
```

`Is that it?` I hear you say. Well, if things were that simple in this world I wouldn't be writing this post 😅.

So why wouldn't this work? Sequelize has a weird way to work and if we try to define this association in their respective files (as we do in JS) Sequelize will throw this error:

```bash
throw new Error("${source.name}.${_.lowerFirst(Type.name)} called with something that's not a subclass of Sequelize.Model");
      ^
"Error: Book.belongsTo called with something that's not a subclass of Sequelize.Model"
```

At first, I thought I was missing something or that I didn't type the Author Model as a Sequelize Model but after double-checking everything was good, TypeScript was not complaining that I was trying to call the `belongsTo` method with something else that's not a Sequelize Model. So I was wondering why is this happening? 🤔

I was reading the Sequelize documentation and posts from around the web and didn't find anything that would work, except someone (sorry didn't find the link to the StackOverflow thread) that suggested that one should put their association in one Model file. I was skeptical about it since I said to myself: _how would this work since I'm exporting the same model that is in my file_. However, I gave it a try. I moved the `belongsTo` call to the `models/author.ts` file:

```TS
// model/author.ts
import Book from './book';

// ... a bunch of code here

Book.belongsTo(Author, {
  foreignKey: 'authorId',
  as: 'author'
});
```

And Lord and behold my project was running:

```bash
The library Server is running on http://localhost:3000
```

It is weird but it seems that you have to define the `belongsTo` association in the same file that you set the `hasMany` or the `hasOne` association.

### Conclusion <a name="chapter-5"></a>

In the end, setting up Sequelize with TypeScript wasn't hard at all, and now we know which properties belong to which model.

For me, it was a valuable experience since I thought that translating my JS files to TS would just be done by renaming the file and that would be it since I believed that Sequelize would handle the typing work in the background.

**Note:** I know that there is a package out there called [sequelize-typescript](https://www.npmjs.com/package/sequelize-typescript) but I didn't feel like adding another dependency to my project, plus this package is not maintained by the Sequelize team. However, I just tried it and it's pretty cool, so give it a try if you don't want a setup like mine.

By the end of the day, I don't feel like having another dependency in my project, and I was able to accomplish the goal of adding TypeScript to Sequlize without a third-party library just with pure TS.

Sorry for the long post, but I hope you find it useful if you are trying to use Sequelize and TypeScript together.👋🏾