In the age of Prisma and Drizzle, Sequelize still holds its ground as a mature, battle-tested ORM with excellent support for raw SQL, transactional workflows, and complex associations. But if you’ve ever tried to use Sequelize with TypeScript, you’ve probably bumped into the usual pain points: model typing, messy associations, or that dreaded return of any.

This guide is for developers who want to use Sequelize in a TypeScript project the right way — with fully typed models, associations, queries, and clean project structure.


🛠️ Project Setup

Let’s start fresh:

1. Initialize your project

mkdir sequelize-ts-guide && cd sequelize-ts-guide
npm init -y
npm install sequelize sequelize-typescript pg reflect-metadata
npm install -D typescript ts-node @types/node

2. TypeScript config (tsconfig.json)

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "strict": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "dist"
  },
  "include": ["src/**/*"]
}

3. Sequelize initialization (src/db.ts)

import { Sequelize } from 'sequelize-typescript';
import { User } from './models/User';
import { Post } from './models/Post';

export const sequelize = new Sequelize({
  dialect: 'postgres',
  database: 'sequelize_ts_demo',
  username: 'postgres',
  password: 'password',
  models: [User, Post],
  logging: false,
});

📦 Defining Models the Right Way

Use sequelize-typescript decorators for clean typing and class-based models.

Example: User model

// src/models/User.ts
import { Table, Column, Model, HasMany } from 'sequelize-typescript';
import { Post } from './Post';

@Table
export class User extends Model<User> {
  @Column
  name!: string;

  @Column
  email!: string;

  @HasMany(() => Post)
  posts!: Post[];
}

Example: Post model

// src/models/Post.ts
import { Table, Column, Model, ForeignKey, BelongsTo } from 'sequelize-typescript';
import { User } from './User';

@Table
export class Post extends Model<Post> {
  @Column
  title!: string;

  @ForeignKey(() => User)
  @Column
  userId!: number;

  @BelongsTo(() => User)
  user!: User;
}

🧠 Typing Models with Generics

Sequelize models can be typed using Model, InferCreationAttributes>.
But using sequelize-typescript, that’s already handled for you. You get autocomplete and strict typings for both creation and querying.


🔍 Type-Safe Queries

const user = await User.findOne({
  where: { email: '[email protected]' },
  include: [User.associations.posts],
});

if (user) {
  user.posts.forEach(post => {
    console.log(post.title); // Fully typed!
  });
}

Avoid using raw SQL without typing the response. Sequelize will return any otherwise.


🔗 Associations Done Right

All you need is decorators and proper model imports. No need to define association functions manually unless you want advanced behavior.

hasMany + belongsTo

// Already defined above. Access with `user.posts` or `post.user`

🧱 Project Structure

src/
├── db.ts
├── models/
│   ├── User.ts
│   └── Post.ts
├── services/
│   └── userService.ts
└── index.ts

Use services to abstract DB logic from routes/controllers.

Example service:

export const findUserWithPosts = async (id: number) => {
  return await User.findByPk(id, {
    include: [User.associations.posts],
  });
};

✅ Best Practices

  • Use sequelize-typescript for decorator-based, strongly-typed models
  • Avoid any by never using raw query results without parsing
  • Use Zod (or Yup) for input validation before hitting Sequelize
  • Write repository-style services for clean logic separation

🧨 Common Gotchas

  • Circular imports in model associations → use lazy imports in decorators (() => Model)
  • Forgetting emitDecoratorMetadata or experimentalDecorators → TS won’t infer types correctly
  • Not using sequelize.sync() or migrations properly → model mismatch with DB

🧭 What’s Next?

Now that you’ve got a fully typed Sequelize setup, the next step is understanding how to type complex associations — especially many-to-many relationships.

In next week’s post, we’ll deep dive into:

“Typed Sequelize Associations in TypeScript — A Deep Dive”


👋 Final Thoughts

Sequelize isn’t going anywhere. It’s used in enterprise apps where flexibility, raw SQL access, and deep control over migrations matter.

But to make it shine in a modern TypeScript stack, you need a clean approach — and now you’ve got one.

Happy querying! 🚀