+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 194 of 355

📘 TypeORM: Database ORM with TypeScript

Master TypeORM: database ORM with TypeScript in TypeScript with practical examples, best practices, and real-world applications 🚀

🚀Intermediate
25 min read

Prerequisites

  • Basic understanding of JavaScript 📝
  • TypeScript installation ⚡
  • VS Code or preferred IDE 💻
  • Node.js and npm knowledge 🔧
  • Basic database concepts 🗄️

What you'll learn

  • Understand TypeORM fundamentals 🎯
  • Apply TypeORM in real projects 🏗️
  • Debug common TypeORM issues 🐛
  • Write type-safe database code ✨

🎯 Introduction

Welcome to the world of TypeORM! 🎉 Get ready to supercharge your database interactions with the power of TypeScript. TypeORM is like having a magical translator 🪄 between your TypeScript code and your database - it speaks both languages fluently!

Whether you’re building a blogging platform 📝, an e-commerce store 🛒, or a social media app 📱, TypeORM will make your database operations type-safe, intuitive, and incredibly powerful. No more SQL strings scattered throughout your code!

By the end of this tutorial, you’ll be confidently building robust database applications with TypeORM. Let’s dive into this exciting journey! 🚀

📚 Understanding TypeORM

🤔 What is TypeORM?

TypeORM is like having a super-smart assistant 🤖 that handles all your database conversations. Think of it as a bridge 🌉 between your TypeScript objects and database tables - it automatically converts your JavaScript objects into database records and vice versa!

In TypeScript terms, TypeORM provides:

  • Type Safety: Your database queries are checked at compile-time
  • 🚀 Auto-mapping: Objects become database rows magically
  • 🛡️ Migration Support: Schema changes are handled safely
  • 🎯 Multiple Database Support: Works with PostgreSQL, MySQL, SQLite, and more!

💡 Why Use TypeORM?

Here’s why developers love TypeORM:

  1. Type Safety 🔒: Catch database errors before they happen
  2. Object-Relational Mapping 🗂️: Work with objects, not SQL strings
  3. Active Record & Data Mapper 📊: Choose your preferred pattern
  4. Decorators 🎨: Beautiful, clean entity definitions
  5. Migrations 🔄: Version control for your database schema
  6. Relations 🔗: Handle complex relationships easily

Real-world example: Imagine building a library management system 📚. With TypeORM, you can define a Book entity and a Author entity, and TypeORM handles all the complex relationship mapping automatically!

🔧 Basic Syntax and Usage

📝 Installation and Setup

Let’s get TypeORM up and running:

# 📦 Install TypeORM and database driver
npm install typeorm reflect-metadata sqlite3
# or for PostgreSQL: npm install typeorm reflect-metadata pg

# 🎯 Install TypeScript types
npm install --save-dev @types/node

🏗️ Basic Entity Definition

Here’s your first TypeORM entity:

// 📚 Import decorators
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';

// 🎨 Define a User entity
@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number; // 🆔 Auto-generated ID

  @Column()
  firstName: string; // 👤 User's first name

  @Column()
  lastName: string; // 👤 User's last name

  @Column({ unique: true })
  email: string; // 📧 Unique email address

  @Column({ default: true })
  isActive: boolean; // ✅ User status

  @CreateDateColumn()
  createdAt: Date; // 📅 Auto-generated creation date
}

💡 Explanation: Decorators like @Entity() and @Column() tell TypeORM how to map your class to database tables. It’s like giving your class a database passport! 🛂

🔧 Database Configuration

Set up your database connection:

// 📁 data-source.ts
import { DataSource } from 'typeorm';
import { User } from './entities/User';

// 🎯 Create database connection
export const AppDataSource = new DataSource({
  type: 'sqlite', // 📱 Using SQLite for simplicity
  database: 'library.db',
  synchronize: true, // ⚠️ Auto-sync in development only!
  logging: true, // 📝 See SQL queries in console
  entities: [User], // 🏗️ Register your entities
});

// 🚀 Initialize the connection
AppDataSource.initialize()
  .then(() => {
    console.log('🎉 Database connected successfully!');
  })
  .catch(error => {
    console.error('💥 Database connection failed:', error);
  });

💡 Practical Examples

📚 Example 1: Library Management System

Let’s build a complete library system:

// 📖 Book entity
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany } from 'typeorm';

@Entity()
export class Book {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string; // 📚 Book title

  @Column()
  isbn: string; // 🔢 ISBN number

  @Column('decimal', { precision: 10, scale: 2 })
  price: number; // 💰 Book price

  @Column()
  emoji: string; // 🎨 Fun book emoji

  @ManyToOne(() => Author, author => author.books)
  author: Author; // 👤 Book author

  @OneToMany(() => Review, review => review.book)
  reviews: Review[]; // 📝 Book reviews
}

// ✍️ Author entity
@Entity()
export class Author {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string; // 👤 Author name

  @Column()
  bio: string; // 📝 Author biography

  @Column()
  favoriteEmoji: string; // 😊 Author's favorite emoji

  @OneToMany(() => Book, book => book.author)
  books: Book[]; // 📚 Author's books
}

// 📝 Review entity
@Entity()
export class Review {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  rating: number; // ⭐ Rating (1-5)

  @Column('text')
  comment: string; // 💬 Review comment

  @Column()
  reviewerName: string; // 👤 Reviewer name

  @ManyToOne(() => Book, book => book.reviews)
  book: Book; // 📚 Reviewed book

  @CreateDateColumn()
  createdAt: Date; // 📅 Review date
}

🏪 Example 2: Working with Repositories

Now let’s interact with our database:

// 🏪 BookService - handles all book operations
import { AppDataSource } from './data-source';
import { Book } from './entities/Book';
import { Author } from './entities/Author';

export class BookService {
  private bookRepository = AppDataSource.getRepository(Book);
  private authorRepository = AppDataSource.getRepository(Author);

  // 📚 Create a new book
  async createBook(bookData: {
    title: string;
    isbn: string;
    price: number;
    emoji: string;
    authorName: string;
  }): Promise<Book> {
    // 👤 Find or create author
    let author = await this.authorRepository.findOne({
      where: { name: bookData.authorName }
    });

    if (!author) {
      author = this.authorRepository.create({
        name: bookData.authorName,
        bio: `📝 Biography of ${bookData.authorName}`,
        favoriteEmoji: '✍️'
      });
      await this.authorRepository.save(author);
      console.log(`✨ Created new author: ${author.name}`);
    }

    // 📖 Create book
    const book = this.bookRepository.create({
      title: bookData.title,
      isbn: bookData.isbn,
      price: bookData.price,
      emoji: bookData.emoji,
      author: author
    });

    const savedBook = await this.bookRepository.save(book);
    console.log(`🎉 Created book: ${savedBook.emoji} ${savedBook.title}`);
    return savedBook;
  }

  // 🔍 Find books by author
  async findBooksByAuthor(authorName: string): Promise<Book[]> {
    return await this.bookRepository.find({
      where: { author: { name: authorName } },
      relations: ['author', 'reviews']
    });
  }

  // 📊 Get book statistics
  async getBookStats(): Promise<{
    totalBooks: number;
    averagePrice: number;
    topRatedBook: Book | null;
  }> {
    const totalBooks = await this.bookRepository.count();
    
    const avgPriceResult = await this.bookRepository
      .createQueryBuilder('book')
      .select('AVG(book.price)', 'avgPrice')
      .getRawOne();

    const topRatedBook = await this.bookRepository
      .createQueryBuilder('book')
      .leftJoinAndSelect('book.reviews', 'review')
      .leftJoinAndSelect('book.author', 'author')
      .addSelect('AVG(review.rating)', 'avgRating')
      .groupBy('book.id')
      .orderBy('avgRating', 'DESC')
      .getOne();

    return {
      totalBooks,
      averagePrice: parseFloat(avgPriceResult?.avgPrice || '0'),
      topRatedBook
    };
  }
}

// 🎮 Let's use our service!
async function runExample() {
  const bookService = new BookService();

  // 📚 Create some books
  await bookService.createBook({
    title: 'TypeScript Mastery',
    isbn: '978-1234567890',
    price: 29.99,
    emoji: '📘',
    authorName: 'Sarah Developer'
  });

  await bookService.createBook({
    title: 'React with TypeScript',
    isbn: '978-1234567891',
    price: 34.99,
    emoji: '⚛️',
    authorName: 'Sarah Developer'
  });

  // 🔍 Find Sarah's books
  const sarahBooks = await bookService.findBooksByAuthor('Sarah Developer');
  console.log(`📚 Sarah has written ${sarahBooks.length} books!`);

  // 📊 Get statistics
  const stats = await bookService.getBookStats();
  console.log(`📊 Library Stats:
    📖 Total Books: ${stats.totalBooks}
    💰 Average Price: $${stats.averagePrice.toFixed(2)}
    🏆 Top Book: ${stats.topRatedBook?.emoji} ${stats.topRatedBook?.title}
  `);
}

🚀 Advanced Concepts

🧙‍♂️ Custom Repositories

Create specialized repositories for complex operations:

// 🎯 Custom Book Repository
import { EntityRepository, Repository } from 'typeorm';
import { Book } from '../entities/Book';

@EntityRepository(Book)
export class BookRepository extends Repository<Book> {
  
  // 🔍 Find books with high ratings
  async findTopRatedBooks(minRating: number = 4): Promise<Book[]> {
    return this.createQueryBuilder('book')
      .leftJoinAndSelect('book.reviews', 'review')
      .leftJoinAndSelect('book.author', 'author')
      .where('review.rating >= :minRating', { minRating })
      .groupBy('book.id')
      .having('AVG(review.rating) >= :minRating', { minRating })
      .getMany();
  }

  // 💰 Find books by price range
  async findBooksByPriceRange(minPrice: number, maxPrice: number): Promise<Book[]> {
    return this.createQueryBuilder('book')
      .where('book.price BETWEEN :minPrice AND :maxPrice', { minPrice, maxPrice })
      .leftJoinAndSelect('book.author', 'author')
      .orderBy('book.price', 'ASC')
      .getMany();
  }

  // 🎨 Find books by emoji
  async findBooksByEmoji(emoji: string): Promise<Book[]> {
    return this.find({
      where: { emoji },
      relations: ['author']
    });
  }
}

🔄 Database Migrations

Handle schema changes safely:

// 📁 migration/1640995200000-CreateBookTable.ts
import { MigrationInterface, QueryRunner, Table } from 'typeorm';

export class CreateBookTable1640995200000 implements MigrationInterface {
  
  // ⬆️ Run migration
  public async up(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.createTable(
      new Table({
        name: 'book',
        columns: [
          {
            name: 'id',
            type: 'int',
            isPrimary: true,
            isGenerated: true,
            generationStrategy: 'increment'
          },
          {
            name: 'title',
            type: 'varchar',
            length: '255'
          },
          {
            name: 'isbn',
            type: 'varchar',
            length: '20',
            isUnique: true
          },
          {
            name: 'emoji',
            type: 'varchar',
            length: '10',
            default: "'📚'"
          }
        ]
      })
    );
    console.log('✅ Book table created successfully!');
  }

  // ⬇️ Rollback migration
  public async down(queryRunner: QueryRunner): Promise<void> {
    await queryRunner.dropTable('book');
    console.log('🗑️ Book table dropped!');
  }
}

⚠️ Common Pitfalls and Solutions

😱 Pitfall 1: Forgetting Relations

// ❌ Wrong way - missing relations
const books = await bookRepository.find();
console.log(books[0].author.name); // 💥 Error: author is undefined!

// ✅ Correct way - include relations
const books = await bookRepository.find({
  relations: ['author', 'reviews']
});
console.log(books[0].author.name); // ✅ Works perfectly!

🤯 Pitfall 2: N+1 Query Problem

// ❌ Wrong way - causes N+1 queries
const books = await bookRepository.find();
for (const book of books) {
  const author = await authorRepository.findOne(book.author.id);
  console.log(`${book.title} by ${author.name}`);
}

// ✅ Correct way - single query with joins
const books = await bookRepository.find({
  relations: ['author']
});
books.forEach(book => {
  console.log(`${book.title} by ${book.author.name}`);
});

🚫 Pitfall 3: Synchronize in Production

// ❌ Dangerous - never use in production!
const dataSource = new DataSource({
  type: 'postgres',
  synchronize: true, // 💥 This will drop your data!
  // ... other options
});

// ✅ Safe - use migrations in production
const dataSource = new DataSource({
  type: 'postgres',
  synchronize: false, // ✅ Safe
  migrationsRun: true, // ✅ Run migrations automatically
  migrations: ['dist/migrations/*.js'],
  // ... other options
});

🛠️ Best Practices

  1. 🎯 Use TypeScript Strict Mode: Enable all type checking features
  2. 📝 Define Clear Entities: Make your entities self-documenting
  3. 🔄 Use Migrations: Never use synchronize in production
  4. 🏗️ Repository Pattern: Organize your database logic
  5. ⚡ Optimize Queries: Use QueryBuilder for complex operations
  6. 🛡️ Validate Input: Always validate data before saving
  7. 📊 Monitor Performance: Use logging to track slow queries

🧪 Hands-On Exercise

🎯 Challenge: Build a Social Media Post System

Create a type-safe social media posting system:

📋 Requirements:

  • 👤 Users with profiles and avatars
  • 📝 Posts with content, images, and emojis
  • 👍 Likes and comments on posts
  • 🏷️ Hashtags and mentions
  • 📊 Analytics (post views, engagement)

🚀 Bonus Points:

  • Add follower/following relationships
  • Implement post scheduling
  • Create trending hashtags feature
  • Add post search functionality

💡 Solution

🔍 Click to see solution
// 👤 User entity
@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  username: string;

  @Column()
  email: string;

  @Column()
  avatar: string; // 🖼️ Avatar URL

  @Column()
  favoriteEmoji: string; // 😊 User's favorite emoji

  @OneToMany(() => Post, post => post.author)
  posts: Post[];

  @OneToMany(() => Like, like => like.user)
  likes: Like[];

  @OneToMany(() => Comment, comment => comment.author)
  comments: Comment[];

  @ManyToMany(() => User, user => user.following)
  @JoinTable({ name: 'user_follows' })
  followers: User[];

  @ManyToMany(() => User, user => user.followers)
  following: User[];
}

// 📝 Post entity
@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  id: number;

  @Column('text')
  content: string;

  @Column('json', { nullable: true })
  images: string[]; // 🖼️ Image URLs

  @Column()
  emoji: string; // 🎨 Post emoji

  @Column({ default: 0 })
  views: number; // 👀 View count

  @ManyToOne(() => User, user => user.posts)
  author: User;

  @OneToMany(() => Like, like => like.post)
  likes: Like[];

  @OneToMany(() => Comment, comment => comment.post)
  comments: Comment[];

  @ManyToMany(() => Hashtag, hashtag => hashtag.posts)
  @JoinTable({ name: 'post_hashtags' })
  hashtags: Hashtag[];

  @CreateDateColumn()
  createdAt: Date;
}

// 👍 Like entity
@Entity()
export class Like {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => User, user => user.likes)
  user: User;

  @ManyToOne(() => Post, post => post.likes)
  post: Post;

  @CreateDateColumn()
  createdAt: Date;
}

// 💬 Comment entity
@Entity()
export class Comment {
  @PrimaryGeneratedColumn()
  id: number;

  @Column('text')
  content: string;

  @Column()
  emoji: string; // 💭 Comment emoji

  @ManyToOne(() => User, user => user.comments)
  author: User;

  @ManyToOne(() => Post, post => post.comments)
  post: Post;

  @CreateDateColumn()
  createdAt: Date;
}

// 🏷️ Hashtag entity
@Entity()
export class Hashtag {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  name: string; // #typescript

  @Column({ default: 0 })
  usageCount: number; // 📊 How many times used

  @ManyToMany(() => Post, post => post.hashtags)
  posts: Post[];
}

// 📱 Social Media Service
export class SocialMediaService {
  private userRepository = AppDataSource.getRepository(User);
  private postRepository = AppDataSource.getRepository(Post);
  private likeRepository = AppDataSource.getRepository(Like);

  // 📝 Create a new post
  async createPost(userId: number, postData: {
    content: string;
    emoji: string;
    images?: string[];
    hashtags?: string[];
  }): Promise<Post> {
    const user = await this.userRepository.findOne({
      where: { id: userId }
    });

    if (!user) {
      throw new Error('👤 User not found!');
    }

    const post = this.postRepository.create({
      content: postData.content,
      emoji: postData.emoji,
      images: postData.images || [],
      author: user
    });

    const savedPost = await this.postRepository.save(post);
    console.log(`🎉 Created post: ${savedPost.emoji} ${savedPost.content.substring(0, 50)}...`);
    return savedPost;
  }

  // 👍 Like a post
  async likePost(userId: number, postId: number): Promise<Like> {
    // 🔍 Check if already liked
    const existingLike = await this.likeRepository.findOne({
      where: { user: { id: userId }, post: { id: postId } }
    });

    if (existingLike) {
      throw new Error('❤️ You already liked this post!');
    }

    const like = this.likeRepository.create({
      user: { id: userId },
      post: { id: postId }
    });

    const savedLike = await this.likeRepository.save(like);
    console.log('👍 Post liked successfully!');
    return savedLike;
  }

  // 📊 Get post analytics
  async getPostAnalytics(postId: number): Promise<{
    likes: number;
    comments: number;
    views: number;
    engagement: number;
  }> {
    const post = await this.postRepository.findOne({
      where: { id: postId },
      relations: ['likes', 'comments']
    });

    if (!post) {
      throw new Error('📝 Post not found!');
    }

    const likes = post.likes.length;
    const comments = post.comments.length;
    const views = post.views;
    const engagement = views > 0 ? ((likes + comments) / views) * 100 : 0;

    return { likes, comments, views, engagement: Math.round(engagement) };
  }
}

🎓 Key Takeaways

You’ve learned so much about TypeORM! Here’s what you can now do:

  • Create type-safe entities with beautiful decorators 🎨
  • Handle complex relationships between entities 🔗
  • Write efficient queries using QueryBuilder 🚀
  • Manage database migrations safely 🔄
  • Avoid common pitfalls that trip up beginners 🛡️
  • Build production-ready applications with confidence 💪

Remember: TypeORM is your database superhero! 🦸‍♂️ It handles the complex stuff so you can focus on building amazing features.

🤝 Next Steps

Congratulations! 🎉 You’ve mastered TypeORM fundamentals!

Here’s what to do next:

  1. 💻 Practice with the exercises above
  2. 🏗️ Build a full application using TypeORM
  3. 📚 Explore advanced features like custom decorators
  4. 🌟 Share your TypeORM projects with the community!

Ready to level up your backend game? Let’s move on to our next tutorial where we’ll explore Prisma: Modern Database Toolkit and see how it compares to TypeORM! 🚀

Remember: Every database master was once a beginner. Keep coding, keep learning, and most importantly, have fun building amazing things! 🎉


Happy coding! 🎉🚀✨