Skip to content
Sutando
Esc
↑↓navigate↵open⌘Jpreview

Sutando vs TypeORM: A Modern Active Record Alternative

TypeORM has been a staple in the Node.js ecosystem for years. But as TypeScript has evolved and new ORMs have emerged, TypeORM's limitations have become…


TypeORM has been a staple in the Node.js ecosystem for years. But as TypeScript has evolved and new ORMs have emerged, TypeORM’s limitations have become harder to ignore. In this article, we’ll compare TypeORM with Sutando and show why developers are switching.

TypeORM’s Problems

Before comparing features, let’s address why developers look for TypeORM alternatives:

1. Decorator Instability

TypeORM relies heavily on TypeScript decorators (@Entity, @Column, @OneToMany). The TC39 decorator proposal changed multiple times, and TypeORM still uses the legacy experimental decorator syntax. This creates ongoing compatibility concerns.

2. Maintenance Concerns

TypeORM’s development pace has slowed significantly. Issues pile up, PRs take months to merge, and the community has expressed frustration over the lack of active maintenance.

3. QueryBuilder is String-Based

TypeORM’s QueryBuilder uses string-based column and table names, meaning typos are caught at runtime, not compile time:

// TypeORM: string-based — typos not caught until runtime
const users = await getRepository(User)
  .createQueryBuilder('user')
  .where('user.namme = :name', { name: 'Alice' }) // typo: "namme"
  .getMany();

4. Active Record Mode is Limited

TypeORM supports Active Record via BaseEntity, but the implementation is less polished than dedicated Active Record ORMs. Features like scopes, model events, and soft deletes require extra configuration.

Sutando: A Cleaner Alternative

Sutando solves these problems while keeping the Active Record pattern that TypeORM users are familiar with.

No Decorators Required

Sutando models use plain class properties instead of decorators:

// Sutando: clean, no decorators
class User extends Model {
  table = 'users';
  
  casts = {
    is_admin: 'boolean',
    metadata: 'json',
  };
  
  relationPosts() {
    return this.hasMany(Post, 'user_id');
  }
}
// TypeORM: decorator-heavy
@Entity()
class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;
  
  @Column()
  name: string;
  
  @Column({ type: 'boolean' })
  isAdmin: boolean;
  
  @OneToMany(() => Post, post => post.user)
  posts: Post[];
}

Query Comparison

Sutando:

// Clean method chaining
const users = await User.query()
  .where('active', true)
  .with('posts')
  .orderBy('created_at', 'desc')
  .limit(10)
  .get();

TypeORM (Active Record):

const users = await User.createQueryBuilder('user')
  .leftJoinAndSelect('user.posts', 'posts')
  .where('user.active = :active', { active: true })
  .orderBy('user.created_at', 'DESC')
  .take(10)
  .getMany();

Relationships

Sutando defines relationships as methods that return relation objects:

class User extends Model {
  relationPosts() {
    return this.hasMany(Post, 'user_id');
  }
  
  relationProfile() {
    return this.hasOne(Profile);
  }
  
  relationRoles() {
    return this.belongsToMany(Role, 'user_roles', 'user_id', 'role_id');
  }
}

// Usage
const user = await User.query().with('posts', 'profile', 'roles').find(1);

TypeORM uses decorators:

@Entity()
class User extends BaseEntity {
  @OneToMany(() => Post, post => post.user)
  posts: Post[];
  
  @OneToOne(() => Profile, profile => profile.user)
  profile: Profile;
  
  @ManyToMany(() => Role)
  @JoinTable({ name: 'user_roles' })
  roles: Role[];
}

// Usage
const user = await User.createQueryBuilder('user')
  .leftJoinAndSelect('user.posts', 'posts')
  .leftJoinAndSelect('user.profile', 'profile')
  .leftJoinAndSelect('user.roles', 'roles')
  .where('user.id = :id', { id: 1 })
  .getOne();

Feature Comparison

Feature Sutando TypeORM
Pattern Active Record Active Record + Data Mapper
Decorators Not required Required
Schema definition Class properties Decorators (@Entity, @Column)
Query builder Method chaining String-based QueryBuilder
Relationships Method-based (hasMany, etc.) Decorator-based (@OneToMany, etc.)
Eager loading with('posts.comments') leftJoinAndSelect (manual)
Model events Built-in Subscriber / Listener classes
Soft deletes Built-in @DeleteDateColumn decorator
Query scopes Built-in Not available
Migrations Schema builder Migration classes
Bundle size Small Large
Maintenance Active Slow
Database support MySQL, PostgreSQL, SQLite MySQL, PostgreSQL, SQLite, SQL Server, MongoDB

Migration Guide: TypeORM to Sutando

Step 1: Replace Entity Definitions

// Before (TypeORM)
@Entity()
class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;
  
  @Column()
  name: string;
  
  @Column({ unique: true })
  email: string;
  
  @CreateDateColumn()
  createdAt: Date;
  
  @OneToMany(() => Post, post => post.user)
  posts: Post[];
}

// After (Sutando)
class User extends Model {
  table = 'users';
  
  relationPosts() {
    return this.hasMany(Post, 'user_id');
  }
}

Step 2: Replace Queries

// Before (TypeORM)
const user = await User.findOne({ where: { id: 1 }, relations: ['posts'] });
const users = await User.find({ where: { active: true }, take: 10 });

// After (Sutando)
const user = await User.query().with('posts').find(1);
const users = await User.query().where('active', true).limit(10).get();

Step 3: Replace Create/Update/Delete

// Before (TypeORM)
const user = User.create({ name: 'Alice', email: 'alice@example.com' });
await user.save();

// After (Sutando)
const user = new User();
user.name = 'Alice';
user.email = 'alice@example.com';
await user.save();
// or: const user = await User.query().create({ name: 'Alice', email: 'alice@example.com' });

When to Switch from TypeORM to Sutando

  • You’re tired of decorator-related issues
  • You want cleaner, more readable query syntax
  • You need built-in query scopes and model events
  • You want a more actively maintained ORM
  • You’re looking for a Laravel Eloquent-like experience
  • You want to reduce bundle size

Conclusion

TypeORM served the Node.js community well, but its decorator dependency, string-based queries, and maintenance issues make it hard to recommend for new projects. Sutando offers the same Active Record pattern with a cleaner API, no decorator requirement, and built-in features that TypeORM lacks.

If you’re starting a new project or considering a TypeORM replacement, give Sutando a try: npm install sutando — or read the documentation.