Skip to content

Typescript_foundations

Chapter 2: TypeScript Foundations for TypeORM

Section titled “Chapter 2: TypeScript Foundations for TypeORM”

Decorators are the foundation of TypeORM. They allow you to add metadata to classes and their members.

Decorator Architecture
================================================================================
Decorator Types
|
+-------------------------+-------------------------+
| | | | |
v v v v v
+--------+ +--------+ +--------+ +--------+ +--------+
| Class | | Method | | Property| | Param | | Accessor|
| Decor. | | Decor. | | Decor. | | Decor. | | Decor. |
+--------+ +--------+ +--------+ +--------+ +--------+
================================================================================
Class Decorators in TypeORM
+------------------------------------------------------------------+
| |
| @Entity('users') // Marks class as database table |
| class User { |
| // ... |
| } |
| |
| How it works: |
| |
| +------------------------+ |
| | TypeScript Code | |
| | @Entity('users') | |
| | class User {} | |
| +------------------------+ |
| | |
| v |
| +------------------------+ |
| | Decorator Factory | |
| | Adds metadata to | |
| | class constructor | |
| +------------------------+ |
| | |
| v |
| +------------------------+ |
| | TypeORM reads | |
| | metadata and creates | |
| | table 'users' | |
| +------------------------+ |
| |
+------------------------------------------------------------------+
Property Decorators in TypeORM
+------------------------------------------------------------------+
| |
| @Entity('users') |
| class User { |
| @PrimaryGeneratedColumn() |
| id: number; |
| |
| @Column({ length: 100 }) |
| name: string; |
| |
| @Column({ unique: true }) |
| email: string; |
| } |
| |
| Generated SQL: |
| +-----------------------------------------------------------+ |
| | CREATE TABLE users ( | |
| | id SERIAL PRIMARY KEY, | |
| | name VARCHAR(100), | |
| | email VARCHAR(255) UNIQUE | |
| | ); | |
| +-----------------------------------------------------------+ |
| |
+------------------------------------------------------------------+
tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "ES2021",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}

TypeORM uses reflect-metadata to store and retrieve decorator metadata.

Reflect Metadata Flow
================================================================================
1. Import reflect-metadata
+------------------------------------------------------------------+
| import 'reflect-metadata'; |
| |
| // This must be imported once at application entry |
+------------------------------------------------------------------+
|
v
2. Decorators add metadata
+------------------------------------------------------------------+
| @Column() |
| name: string; |
| |
| // Internally: |
| Reflect.defineMetadata( |
| 'typeorm:column', |
| { type: 'varchar' }, |
| User.prototype, |
| 'name' |
| ); |
+------------------------------------------------------------------+
|
v
3. TypeORM reads metadata
+------------------------------------------------------------------+
| const metadata = Reflect.getMetadata( |
| 'typeorm:column', |
| User.prototype, |
| 'name' |
| ); |
+------------------------------------------------------------------+
================================================================================

TypeScript’s type inference is crucial for TypeORM’s type safety.

Type Inference Examples
+------------------------------------------------------------------+
| |
| // Type is inferred from property type |
| @Column() |
| name: string; // TypeORM knows this is VARCHAR |
| |
| @Column() |
| age: number; // TypeORM knows this is INTEGER |
| |
| @Column() |
| isActive: boolean; // TypeORM knows this is BOOLEAN |
| |
| @Column() |
| createdAt: Date; // TypeORM knows this is TIMESTAMP |
| |
| @Column('simple-array') |
| tags: string[]; // TypeORM knows this is TEXT array |
| |
+------------------------------------------------------------------+
TypeScript to SQL Type Mapping
+------------------------------------------------------------------+
| |
| TypeScript Type | PostgreSQL Type | MySQL Type |
| -------------------|--------------------|--------------------|
| string | varchar/text | varchar/text |
| number | integer/decimal | int/decimal |
| boolean | boolean | tinyint(1) |
| Date | timestamp | datetime |
| Buffer | bytea | blob |
| object | json/jsonb | json |
| string[] | text[] | json |
| |
+------------------------------------------------------------------+

TypeORM makes extensive use of generics for type safety.

Generics Usage
+------------------------------------------------------------------+
| |
| // Repository is generic |
| interface Repository<Entity> { |
| find(): Promise<Entity[]>; |
| findOne(options): Promise<Entity | null>; |
| save(entity: Entity): Promise<Entity>; |
| delete(entity: Entity): Promise<void>; |
| } |
| |
| // Usage with specific entity |
| const userRepo: Repository<User> = dataSource.getRepository(User);
| |
| // TypeScript knows the return type |
| const user: User | null = await userRepo.findOne({ |
| where: { id: 1 } |
| }); |
| |
| // TypeScript knows the parameter type |
| const savedUser: User = await userRepo.save({ |
| name: 'John', |
| email: 'john@example.com' |
| }); |
| |
+------------------------------------------------------------------+
Generic Constraints
+------------------------------------------------------------------+
| |
| // BaseEntity provides common methods |
| abstract class BaseEntity { |
| @PrimaryGeneratedColumn() |
| id: number; |
| |
| @CreateDateColumn() |
| createdAt: Date; |
| |
| @UpdateDateColumn() |
| updatedAt: Date; |
| } |
| |
| // Entities extend BaseEntity |
| @Entity() |
| class User extends BaseEntity { |
| @Column() |
| name: string; |
| } |
| |
| // Repository with base entity constraints |
| function getRepository<T extends BaseEntity>( |
| entity: new () => T |
| ): Repository<T> { |
| return dataSource.getRepository(entity); |
| } |
| |
+------------------------------------------------------------------+

TypeORM uses union types for flexible querying.

Union Types in TypeORM
+------------------------------------------------------------------+
| |
| // FindOneOptions uses union types |
| interface FindOneOptions<Entity> { |
| where?: |
| | FindOptionsWhere<Entity>[] |
| | FindOptionsWhere<Entity>; |
| |
| relations?: |
| | string[] |
| | FindOptionsRelations<Entity>; |
| |
| select?: |
| | (keyof Entity)[] |
| | FindOptionsSelect<Entity>; |
| } |
| |
| // Usage |
| await userRepo.findOne({ |
| where: { id: 1 } // Object |
| }); |
| |
| await userRepo.findOne({ |
| where: [ // Array of objects |
| { id: 1 }, |
| { email: 'john@example.com' } |
| ] |
| }); |
| |
+------------------------------------------------------------------+
Type Guards for Entity Checking
+------------------------------------------------------------------+
| |
| // User-defined type guard |
| function isUser(entity: unknown): entity is User { |
| return ( |
| typeof entity === 'object' && |
| entity !== null && |
| 'email' in entity |
| ); |
| } |
| |
| // Usage |
| const result = await repo.findOne({ where: { id: 1 } }); |
| |
| if (isUser(result)) { |
| console.log(result.email); // TypeScript knows it's User |
| } |
| |
+------------------------------------------------------------------+

All TypeORM database operations are asynchronous.

Async/Await Flow
================================================================================
+------------------+ +------------------+
| Service Layer | | Database |
| | | |
| async getUser() | | |
| | | | |
| v | | |
| await repo |------->| Execute Query |
| .findOne() | | |
| | |<-------| Return Result |
| v | | |
| return user | | |
+------------------+ +------------------+
================================================================================
// Sequential operations
async function getUserWithPosts(userId: number) {
const user = await userRepo.findOne({ where: { id: userId } });
const posts = await postRepo.find({ where: { authorId: userId } });
return { user, posts };
}
// Parallel operations
async function getUserData(userId: number) {
const [user, posts, comments] = await Promise.all([
userRepo.findOne({ where: { id: userId } }),
postRepo.find({ where: { authorId: userId } }),
commentRepo.find({ where: { userId } }),
]);
return { user, posts, comments };
}
// Error handling
async function safeGetUser(userId: number) {
try {
const user = await userRepo.findOne({ where: { id: userId } });
if (!user) {
throw new Error('User not found');
}
return user;
} catch (error) {
console.error('Failed to get user:', error);
throw error;
}
}

Understanding when to use interfaces vs types in TypeORM.

Interface vs Type for DTOs
+------------------------------------------------------------------+
| |
| // Interface for DTOs (common pattern) |
| interface CreateUserDto { |
| name: string; |
| email: string; |
| password: string; |
| } |
| |
| // Type for complex combinations |
| type UserResponse = Omit<User, 'password'> & { |
| postsCount: number; |
| }; |
| |
| // Partial for updates |
| type UpdateUserDto = Partial<CreateUserDto>; |
| |
| // Pick for specific fields |
| type UserCredentials = Pick<User, 'email' | 'password'>; |
| |
+------------------------------------------------------------------+

TypeScript utility types are extremely useful with TypeORM.

Common Utility Types
+------------------------------------------------------------------+
| |
| // Partial<T> - All properties optional |
| async update(id: number, data: Partial<User>) { |
| await userRepo.update(id, data); |
| } |
| |
| // Required<T> - All properties required |
| function validateUser(user: Required<User>) { |
| // All fields must be present |
| } |
| |
| // Pick<T, K> - Select specific properties |
| type PublicUser = Pick<User, 'id' | 'name' | 'email'>; |
| |
| // Omit<T, K> - Exclude specific properties |
| type SafeUser = Omit<User, 'password' | 'salt'>; |
| |
| // ReturnType<T> - Get return type of function |
| type UserResult = ReturnType<typeof userService.findOne>; |
| |
| // Parameters<T> - Get parameter types of function |
| type CreateUserParams = Parameters<typeof userService.create>;
| |
+------------------------------------------------------------------+

// Complete entity with TypeScript best practices
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ length: 100 })
name: string;
@Column({ unique: true })
email: string;
@Column({ select: false })
password: string;
@Column({ default: true })
isActive: boolean;
@Column({ type: 'enum', enum: UserRole, default: UserRole.USER })
role: UserRole;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@DeleteDateColumn()
deletedAt: Date | null;
@OneToMany(() => Post, post => post.author)
posts: Post[];
@OneToOne(() => Profile, profile => profile.user)
profile: Profile;
}
// DTOs with validation
export class CreateUserDto {
@IsString()
@IsNotEmpty()
@MaxLength(100)
name: string;
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
}
// Response type
export type UserResponse = Omit<User, 'password' | 'deletedAt'>;

TypeScript Best Practices for TypeORM
+------------------------------------------------------------------+
| |
| 1. Always enable strict mode in tsconfig.json |
| +------------------------------------------------------+ |
| | { | |
| | "compilerOptions": { | |
| | "strict": true, | |
| | "strictPropertyInitialization": false | |
| | } | |
| | } | |
| +------------------------------------------------------+ |
| |
| 2. Use definite assignment assertion for relations |
| @OneToMany(() => Post, post => post.author) |
| posts!: Post[]; |
| |
| 3. Create specific types for each use case |
| - Entity for database |
| - DTO for input validation |
| - Response type for output |
| |
| 4. Use enums for fixed values |
| enum UserRole { ADMIN = 'admin', USER = 'user' } |
| |
| 5. Avoid any - use unknown when type is uncertain |
| |
+------------------------------------------------------------------+

Chapter 3: Database Fundamentals & SQL


Last Updated: February 2026