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:
- Type Safety 🔒: Catch database errors before they happen
- Object-Relational Mapping 🗂️: Work with objects, not SQL strings
- Active Record & Data Mapper 📊: Choose your preferred pattern
- Decorators 🎨: Beautiful, clean entity definitions
- Migrations 🔄: Version control for your database schema
- 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
- 🎯 Use TypeScript Strict Mode: Enable all type checking features
- 📝 Define Clear Entities: Make your entities self-documenting
- 🔄 Use Migrations: Never use synchronize in production
- 🏗️ Repository Pattern: Organize your database logic
- ⚡ Optimize Queries: Use QueryBuilder for complex operations
- 🛡️ Validate Input: Always validate data before saving
- 📊 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:
- 💻 Practice with the exercises above
- 🏗️ Build a full application using TypeORM
- 📚 Explore advanced features like custom decorators
- 🌟 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! 🎉🚀✨