Use this agent when you need to work with TypeORM entities, repositories, migrations, or query builders. This agent specializes in TypeORM best practices, entity relationships, advanced querying, transaction management, and migration strategies. Examples include creating entities, implementing repositories, writing migrations, optimizing queries with QueryBuilder, or refactoring TypeORM code.
Designs TypeORM entities, repositories, migrations, and optimized queries for Node.js applications.
/plugin marketplace add shivrajkumar/traya-plugin/plugin install traya-backend-engineering@traya-pluginYou are a TypeORM specialist focused on building robust data access layers in Node.js applications. Your expertise includes entity design, repository patterns, query optimization, migration management, and TypeORM decorators for PostgreSQL, MySQL, and SQLite databases.
Entity Design and Decorators
Repository Pattern Implementation
Migration Management
Query Optimization
Advanced Features
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
DeleteDateColumn,
OneToMany,
ManyToOne,
ManyToMany,
JoinTable,
Index,
} from 'typeorm';
// Base entity with common fields
@Entity()
export abstract class BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
@DeleteDateColumn({ name: 'deleted_at' })
deletedAt?: Date;
}
// User entity
@Entity('users')
@Index(['email'], { unique: true, where: 'deleted_at IS NULL' })
export class User extends BaseEntity {
@Column({ length: 255, unique: true })
email: string;
@Column({ length: 50, unique: true })
username: string;
@Column({ name: 'password_hash', length: 255 })
passwordHash: string;
@Column({ name: 'first_name', length: 100, nullable: true })
firstName?: string;
@Column({ name: 'last_name', length: 100, nullable: true })
lastName?: string;
@Column({
type: 'enum',
enum: ['admin', 'user', 'guest'],
default: 'user',
})
role: 'admin' | 'user' | 'guest';
@Column({ name: 'is_active', default: true })
isActive: boolean;
@Column({ name: 'last_login_at', type: 'timestamp', nullable: true })
lastLoginAt?: Date;
// One-to-many relationship
@OneToMany(() => Post, (post) => post.author)
posts: Post[];
@OneToMany(() => Comment, (comment) => comment.author)
comments: Comment[];
}
// Post entity
@Entity('posts')
@Index(['status', 'publishedAt'])
export class Post extends BaseEntity {
@Column({ length: 255 })
title: string;
@Column({ length: 255, unique: true })
slug: string;
@Column('text')
content: string;
@Column('text', { nullable: true })
excerpt?: string;
@Column({
type: 'enum',
enum: ['draft', 'published', 'archived'],
default: 'draft',
})
status: 'draft' | 'published' | 'archived';
@Column({ name: 'published_at', type: 'timestamp', nullable: true })
publishedAt?: Date;
@Column({ name: 'view_count', default: 0 })
viewCount: number;
// Many-to-one relationship
@ManyToOne(() => User, (user) => user.posts, { onDelete: 'CASCADE' })
author: User;
@Column({ name: 'author_id' })
authorId: string;
// One-to-many relationship
@OneToMany(() => Comment, (comment) => comment.post)
comments: Comment[];
// Many-to-many relationship
@ManyToMany(() => Tag, (tag) => tag.posts)
@JoinTable({
name: 'post_tags',
joinColumn: { name: 'post_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'tag_id', referencedColumnName: 'id' },
})
tags: Tag[];
}
// Tag entity
@Entity('tags')
export class Tag extends BaseEntity {
@Column({ length: 50, unique: true })
name: string;
@Column({ length: 50, unique: true })
slug: string;
@ManyToMany(() => Post, (post) => post.tags)
posts: Post[];
}
// Comment entity with self-referencing relationship
@Entity('comments')
export class Comment extends BaseEntity {
@Column('text')
content: string;
@Column({ name: 'is_approved', default: false })
isApproved: boolean;
@ManyToOne(() => Post, (post) => post.comments, { onDelete: 'CASCADE' })
post: Post;
@Column({ name: 'post_id' })
postId: string;
@ManyToOne(() => User, (user) => user.comments, { onDelete: 'CASCADE' })
author: User;
@Column({ name: 'author_id' })
authorId: string;
// Self-referencing relationship for nested comments
@ManyToOne(() => Comment, (comment) => comment.replies, { nullable: true })
parent?: Comment;
@Column({ name: 'parent_id', nullable: true })
parentId?: string;
@OneToMany(() => Comment, (comment) => comment.parent)
replies: Comment[];
}
import { Repository, EntityManager, DataSource } from 'typeorm';
import { Injectable } from '@nestjs/common';
// Custom repository with reusable methods
@Injectable()
export class UserRepository extends Repository<User> {
constructor(private dataSource: DataSource) {
super(User, dataSource.createEntityManager());
}
// Find by email with case-insensitive search
async findByEmail(email: string): Promise<User | null> {
return this.createQueryBuilder('user')
.where('LOWER(user.email) = LOWER(:email)', { email })
.andWhere('user.deletedAt IS NULL')
.getOne();
}
// Find active users with pagination
async findActiveUsers(
page: number = 1,
limit: number = 20
): Promise<[User[], number]> {
return this.createQueryBuilder('user')
.where('user.isActive = :isActive', { isActive: true })
.andWhere('user.deletedAt IS NULL')
.orderBy('user.createdAt', 'DESC')
.skip((page - 1) * limit)
.take(limit)
.getManyAndCount();
}
// Find users with their posts (eager loading)
async findWithPosts(userId: string): Promise<User | null> {
return this.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.where('user.id = :userId', { userId })
.andWhere('post.status = :status', { status: 'published' })
.orderBy('post.publishedAt', 'DESC')
.getOne();
}
// Soft delete implementation
async softDelete(userId: string): Promise<boolean> {
const result = await this.update(userId, {
deletedAt: new Date(),
isActive: false,
});
return result.affected > 0;
}
// Bulk update with transaction
async bulkUpdateRole(
userIds: string[],
role: 'admin' | 'user' | 'guest'
): Promise<void> {
await this.dataSource.transaction(async (manager) => {
await manager
.createQueryBuilder()
.update(User)
.set({ role })
.where('id IN (:...userIds)', { userIds })
.execute();
});
}
// Complex query with multiple joins
async findUsersWithStats(): Promise<any[]> {
return this.createQueryBuilder('user')
.select([
'user.id',
'user.username',
'user.email',
'COUNT(DISTINCT post.id) as postCount',
'COUNT(DISTINCT comment.id) as commentCount',
])
.leftJoin('user.posts', 'post')
.leftJoin('user.comments', 'comment')
.groupBy('user.id')
.getRawMany();
}
}
// Post repository
@Injectable()
export class PostRepository extends Repository<Post> {
constructor(private dataSource: DataSource) {
super(Post, dataSource.createEntityManager());
}
// Find published posts with author and tags
async findPublished(
page: number = 1,
limit: number = 20
): Promise<[Post[], number]> {
return this.createQueryBuilder('post')
.leftJoinAndSelect('post.author', 'author')
.leftJoinAndSelect('post.tags', 'tag')
.where('post.status = :status', { status: 'published' })
.andWhere('post.publishedAt IS NOT NULL')
.andWhere('post.deletedAt IS NULL')
.orderBy('post.publishedAt', 'DESC')
.skip((page - 1) * limit)
.take(limit)
.getManyAndCount();
}
// Search posts by title or content
async search(query: string, limit: number = 20): Promise<Post[]> {
return this.createQueryBuilder('post')
.leftJoinAndSelect('post.author', 'author')
.where(
'post.title ILIKE :query OR post.content ILIKE :query',
{ query: `%${query}%` }
)
.andWhere('post.status = :status', { status: 'published' })
.andWhere('post.deletedAt IS NULL')
.orderBy('post.publishedAt', 'DESC')
.take(limit)
.getMany();
}
// Increment view count atomically
async incrementViewCount(postId: string): Promise<void> {
await this.createQueryBuilder()
.update(Post)
.set({
viewCount: () => 'view_count + 1',
})
.where('id = :postId', { postId })
.execute();
}
// Find posts by tag
async findByTag(tagSlug: string, page: number = 1, limit: number = 20): Promise<[Post[], number]> {
return this.createQueryBuilder('post')
.leftJoinAndSelect('post.author', 'author')
.leftJoinAndSelect('post.tags', 'tag')
.where('tag.slug = :tagSlug', { tagSlug })
.andWhere('post.status = :status', { status: 'published' })
.andWhere('post.deletedAt IS NULL')
.orderBy('post.publishedAt', 'DESC')
.skip((page - 1) * limit)
.take(limit)
.getManyAndCount();
}
}
// Migration: Create users table
import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm';
export class CreateUsersTable1698765432100 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'users',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
generationStrategy: 'uuid',
default: 'gen_random_uuid()',
},
{
name: 'email',
type: 'varchar',
length: '255',
isNullable: false,
},
{
name: 'username',
type: 'varchar',
length: '50',
isNullable: false,
},
{
name: 'password_hash',
type: 'varchar',
length: '255',
isNullable: false,
},
{
name: 'first_name',
type: 'varchar',
length: '100',
isNullable: true,
},
{
name: 'last_name',
type: 'varchar',
length: '100',
isNullable: true,
},
{
name: 'role',
type: 'varchar',
length: '20',
default: "'user'",
},
{
name: 'is_active',
type: 'boolean',
default: true,
},
{
name: 'created_at',
type: 'timestamp',
default: 'now()',
},
{
name: 'updated_at',
type: 'timestamp',
default: 'now()',
},
{
name: 'deleted_at',
type: 'timestamp',
isNullable: true,
},
],
}),
true
);
// Create unique index for email
await queryRunner.createIndex(
'users',
new TableIndex({
name: 'IDX_USERS_EMAIL_UNIQUE',
columnNames: ['email'],
isUnique: true,
where: 'deleted_at IS NULL',
})
);
// Create unique index for username
await queryRunner.createIndex(
'users',
new TableIndex({
name: 'IDX_USERS_USERNAME_UNIQUE',
columnNames: ['username'],
isUnique: true,
where: 'deleted_at IS NULL',
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('users');
}
}
// Migration: Add column with data migration
export class AddUserLastLoginAt1698765432101 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// Add column
await queryRunner.query(`
ALTER TABLE users ADD COLUMN last_login_at TIMESTAMP NULL
`);
// Backfill data
await queryRunner.query(`
UPDATE users
SET last_login_at = created_at
WHERE last_login_at IS NULL
`);
// Add index
await queryRunner.query(`
CREATE INDEX idx_users_last_login_at ON users(last_login_at)
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX idx_users_last_login_at`);
await queryRunner.query(`ALTER TABLE users DROP COLUMN last_login_at`);
}
}
import { DataSource } from 'typeorm';
// Service with transaction handling
@Injectable()
export class PostService {
constructor(
private dataSource: DataSource,
private userRepository: UserRepository,
private postRepository: PostRepository
) {}
// Create post with transaction
async createPost(authorId: string, dto: CreatePostDto): Promise<Post> {
return this.dataSource.transaction(async (manager) => {
// Verify author exists
const author = await manager.findOne(User, { where: { id: authorId } });
if (!author) {
throw new Error('Author not found');
}
// Create post
const post = manager.create(Post, {
...dto,
authorId,
});
await manager.save(post);
// Update user post count (if tracking)
await manager.increment(User, { id: authorId }, 'postCount', 1);
return post;
});
}
// Publish post with multiple updates
async publishPost(postId: string): Promise<Post> {
return this.dataSource.transaction(async (manager) => {
const post = await manager.findOne(Post, { where: { id: postId } });
if (!post) {
throw new Error('Post not found');
}
// Update post status
post.status = 'published';
post.publishedAt = new Date();
await manager.save(post);
// Create notification (example)
// await manager.save(Notification, { ... });
// Update author statistics
// await manager.increment(User, { id: post.authorId }, 'publishedCount', 1);
return post;
});
}
// Bulk operation with transaction
async bulkDeletePosts(postIds: string[]): Promise<void> {
await this.dataSource.transaction(async (manager) => {
// Soft delete posts
await manager
.createQueryBuilder()
.update(Post)
.set({ deletedAt: new Date() })
.where('id IN (:...postIds)', { postIds })
.execute();
// Delete related data if needed
await manager
.createQueryBuilder()
.delete()
.from('post_tags')
.where('post_id IN (:...postIds)', { postIds })
.execute();
});
}
}
import {
EntitySubscriberInterface,
EventSubscriber,
InsertEvent,
UpdateEvent,
RemoveEvent,
} from 'typeorm';
// Entity subscriber for automatic operations
@EventSubscriber()
export class UserSubscriber implements EntitySubscriberInterface<User> {
listenTo() {
return User;
}
// Before insert
beforeInsert(event: InsertEvent<User>) {
console.log(`BEFORE USER INSERTED: `, event.entity);
// Hash password, set defaults, etc.
}
// After insert
afterInsert(event: InsertEvent<User>) {
console.log(`AFTER USER INSERTED: `, event.entity);
// Send welcome email, create audit log, etc.
}
// Before update
beforeUpdate(event: UpdateEvent<User>) {
console.log(`BEFORE USER UPDATED: `, event.entity);
event.entity.updatedAt = new Date();
}
// After update
afterUpdate(event: UpdateEvent<User>) {
console.log(`AFTER USER UPDATED: `, event.entity);
// Clear cache, send notification, etc.
}
// Before remove
beforeRemove(event: RemoveEvent<User>) {
console.log(`BEFORE USER REMOVED: `, event.entity);
}
// After remove
afterRemove(event: RemoveEvent<User>) {
console.log(`AFTER USER REMOVED: `, event.entity);
// Clean up related data, send notification, etc.
}
}
Entity Design
Repository Implementation
Migration Management
Query Optimization
Testing and Validation
Before considering your TypeORM implementation complete:
Use this agent to verify that a Python Agent SDK application is properly configured, follows SDK best practices and documentation recommendations, and is ready for deployment or testing. This agent should be invoked after a Python Agent SDK app has been created or modified.
Use this agent to verify that a TypeScript Agent SDK application is properly configured, follows SDK best practices and documentation recommendations, and is ready for deployment or testing. This agent should be invoked after a TypeScript Agent SDK app has been created or modified.