Typescript_foundations
Chapter 2: TypeScript Foundations for TypeORM
Section titled “Chapter 2: TypeScript Foundations for TypeORM”Essential TypeScript Concepts for TypeORM
Section titled “Essential TypeScript Concepts for TypeORM”2.1 TypeScript Decorators
Section titled “2.1 TypeScript Decorators”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
Section titled “Class Decorators” 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
Section titled “Property Decorators” 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 | | | | ); | | | +-----------------------------------------------------------+ | | | +------------------------------------------------------------------+Enabling Decorators in TypeScript
Section titled “Enabling Decorators in TypeScript”{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true, "target": "ES2021", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "esModuleInterop": true, "skipLibCheck": true }}2.2 Reflect Metadata API
Section titled “2.2 Reflect Metadata API”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' | | ); | +------------------------------------------------------------------+
================================================================================2.3 Type Inference
Section titled “2.3 Type Inference”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 | | | +------------------------------------------------------------------+Type Mapping
Section titled “Type Mapping” 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 | | | +------------------------------------------------------------------+2.4 Generics in TypeORM
Section titled “2.4 Generics in TypeORM”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
Section titled “Generic Constraints” 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); | | } | | | +------------------------------------------------------------------+2.5 Union Types and Type Guards
Section titled “2.5 Union Types and Type Guards”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
Section titled “Type Guards” 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 | | } | | | +------------------------------------------------------------------+2.6 Async/Await Pattern
Section titled “2.6 Async/Await Pattern”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 | | | +------------------+ +------------------+
================================================================================Async Patterns
Section titled “Async Patterns”// Sequential operationsasync function getUserWithPosts(userId: number) { const user = await userRepo.findOne({ where: { id: userId } }); const posts = await postRepo.find({ where: { authorId: userId } }); return { user, posts };}
// Parallel operationsasync 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 handlingasync 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; }}2.7 Interface vs Type
Section titled “2.7 Interface vs Type”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'>; | | | +------------------------------------------------------------------+2.8 Utility Types for TypeORM
Section titled “2.8 Utility Types for TypeORM”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>; | | +------------------------------------------------------------------+2.9 Practical Example
Section titled “2.9 Practical Example”// 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 validationexport class CreateUserDto { @IsString() @IsNotEmpty() @MaxLength(100) name: string;
@IsEmail() email: string;
@IsString() @MinLength(8) password: string;}
// Response typeexport type UserResponse = Omit<User, 'password' | 'deletedAt'>;2.10 Best Practices
Section titled “2.10 Best Practices” 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 | | | +------------------------------------------------------------------+Next Chapter
Section titled “Next Chapter”Chapter 3: Database Fundamentals & SQL
Last Updated: February 2026