Module_architecture
Chapter 21: Application Architecture Patterns
Section titled “Chapter 21: Application Architecture Patterns”Building Scalable TypeScript Applications with TypeORM
Section titled “Building Scalable TypeScript Applications with TypeORM”21.1 Understanding Application Architecture
Section titled “21.1 Understanding Application Architecture”A well-structured TypeScript application follows clean architecture principles. Whether you’re using Express, Fastify, or any other framework, the core patterns remain the same.
TypeScript Application Architecture ================================================================================
+-------------------+ +-------------------+ +-------------------+ | Presentation | | Application | | Domain | | Layer | | Layer | | Layer | +-------------------+ +-------------------+ +-------------------+ | | | | | | | HTTP Handlers | | Use Cases | | Entities | | Controllers | | Services | | Value Objects | | Middleware | | DTOs | | Repositories | | Validators | | Commands | | Domain Events | | | | Queries | | | +-------------------+ +-------------------+ +-------------------+ | | | v v v +-------------------+ +-------------------+ +-------------------+ | Infrastructure | | Integration | | | | Layer | | Layer | | | +-------------------+ +-------------------+ | | | | | TypeORM Repos | | External APIs | | Database | | Message Queues | | File Storage | | Email Service | | Caching | | Authentication | | | | | ================================================================================21.2 Project Structure
Section titled “21.2 Project Structure”Organize your TypeScript project by feature or layer:
src/├── main.ts # Application entry point├── config/ # Configuration files│ ├── database.ts│ └── app.ts├── entities/ # TypeORM entities (Domain Layer)│ ├── user.entity.ts│ └── product.entity.ts├── repositories/ # Data access (Infrastructure Layer)│ ├── user.repository.ts│ └── product.repository.ts├── services/ # Business logic (Application Layer)│ ├── user.service.ts│ └── product.service.ts├── dto/ # Data Transfer Objects│ ├── create-user.dto.ts│ └── update-user.dto.ts├── controllers/ # HTTP handlers (Presentation Layer)│ ├── user.controller.ts│ └── product.controller.ts├── middleware/ # Express/Fastify middleware│ └── auth.middleware.ts└── utils/ # Helper functions └── validation.ts21.3 Feature-Based Organization
Section titled “21.3 Feature-Based Organization”Alternative structure organized by feature:
src/├── features/│ ├── users/│ │ ├── entities/│ │ │ └── user.entity.ts│ │ ├── repositories/│ │ │ └── user.repository.ts│ │ ├── services/│ │ │ └── user.service.ts│ │ ├── controllers/│ │ │ └── user.controller.ts│ │ ├── dto/│ │ │ └── create-user.dto.ts│ │ └── index.ts # Barrel exports│ └── products/│ └── ...├── shared/│ ├── entities/│ ├── repositories/│ └── services/└── config/21.4 Creating a TypeORM-Based Application
Section titled “21.4 Creating a TypeORM-Based Application”Step 1: Initialize the Project
Section titled “Step 1: Initialize the Project”mkdir my-typeorm-appcd my-typeorm-appnpm init -ynpm install typescript ts-node @types/node typeorm pgnpm install -D @types/node nodemonStep 2: Configure TypeScript
Section titled “Step 2: Configure TypeScript”{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "lib": ["ES2020"], "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "resolveJsonModule": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"]}Step 3: Set Up Database Connection
Section titled “Step 3: Set Up Database Connection”import 'reflect-metadata';import { DataSource } from 'typeorm';import { User } from '../entities/user.entity';import { Product } from '../entities/product.entity';
export const AppDataSource = new DataSource({ type: 'postgres', host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT) || 5432, username: process.env.DB_USER || 'postgres', password: process.env.DB_PASSWORD || 'password', database: process.env.DB_NAME || 'mydb', synchronize: process.env.NODE_ENV !== 'production', logging: process.env.NODE_ENV === 'development', entities: [User, Product], migrations: ['src/migrations/*.ts'], subscribers: [], pool: { max: 20, min: 2, acquire: 30000, idle: 10000, },});
// Initialize connectionexport async function initializeDatabase(): Promise<void> { try { await AppDataSource.initialize(); console.log('Database connection established'); } catch (error) { console.error('Database connection failed:', error); throw error; }}Step 4: Create Entities
Section titled “Step 4: Create Entities”import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany,} from 'typeorm';import { Product } from './product.entity';
@Entity('users')export class User { @PrimaryGeneratedColumn() id: number;
@Column({ length: 100 }) name: string;
@Column({ unique: true }) email: string;
@Column() password: string;
@Column({ default: true }) isActive: boolean;
@OneToMany(() => Product, (product) => product.owner) products: Product[];
@CreateDateColumn() createdAt: Date;
@UpdateDateColumn() updatedAt: Date;}Step 5: Create Repository
Section titled “Step 5: Create Repository”import { Repository, FindOptionsWhere, DeepPartial } from 'typeorm';import { User } from '../entities/user.entity';
export class UserRepository { constructor(private repository: Repository<User>) {}
async findAll(): Promise<User[]> { return this.repository.find(); }
async findOneById(id: number): Promise<User | null> { return this.repository.findOne({ where: { id } as FindOptionsWhere<User> }); }
async findByEmail(email: string): Promise<User | null> { return this.repository.findOne({ where: { email } as FindOptionsWhere<User> }); }
async create(data: DeepPartial<User>): Promise<User> { const user = this.repository.create(data); return this.repository.save(user); }
async update(id: number, data: Partial<User>): Promise<User> { await this.repository.update(id, data); return this.findOneById(id)!; }
async delete(id: number): Promise<void> { await this.repository.delete(id); }
async findWithProducts(id: number): Promise<User | null> { return this.repository.findOne({ where: { id } as FindOptionsWhere<User>, relations: ['products'], }); }}Step 6: Create Service Layer
Section titled “Step 6: Create Service Layer”import { UserRepository } from '../repositories/user.repository';import { CreateUserDto } from '../dto/create-user.dto';import { User } from '../entities/user.entity';
export class UserService { constructor(private userRepository: UserRepository) {}
async getAllUsers(): Promise<User[]> { return this.userRepository.findAll(); }
async getUserById(id: number): Promise<User | null> { return this.userRepository.findOneById(id); }
async getUserWithProducts(id: number): Promise<User | null> { return this.userRepository.findWithProducts(id); }
async createUser(dto: CreateUserDto): Promise<User> { // Check if user exists const existingUser = await this.userRepository.findByEmail(dto.email); if (existingUser) { throw new Error('User with this email already exists'); }
// Hash password (implement based on your needs) const hashedPassword = await this.hashPassword(dto.password);
return this.userRepository.create({ ...dto, password: hashedPassword, }); }
async updateUser(id: number, data: Partial<User>): Promise<User> { const user = await this.userRepository.findOneById(id); if (!user) { throw new Error('User not found'); } return this.userRepository.update(id, data); }
async deleteUser(id: number): Promise<void> { const user = await this.userRepository.findOneById(id); if (!user) { throw new Error('User not found'); } await this.userRepository.delete(id); }
private async hashPassword(password: string): Promise<string> { // Implement password hashing (e.g., using bcrypt) return password; // Placeholder }}Step 7: Create Controller (Express.js example)
Section titled “Step 7: Create Controller (Express.js example)”import { Request, Response, NextFunction } from 'express';import { UserService } from '../services/user.service';import { CreateUserDto } from '../dto/create-user.dto';
export class UserController { constructor(private userService: UserService) {}
async getAllUsers(req: Request, res: Response, next: NextFunction) { try { const users = await this.userService.getAllUsers(); res.json({ success: true, data: users }); } catch (error) { next(error); } }
async getUserById(req: Request, res: Response, next: NextFunction) { try { const id = parseInt(req.params.id); const user = await this.userService.getUserById(id);
if (!user) { return res.status(404).json({ success: false, message: 'User not found' }); }
res.json({ success: true, data: user }); } catch (error) { next(error); } }
async createUser(req: Request, res: Response, next: NextFunction) { try { const dto: CreateUserDto = req.body; const user = await this.userService.createUser(dto); res.status(201).json({ success: true, data: user }); } catch (error) { next(error); } }
async updateUser(req: Request, res: Response, next: NextFunction) { try { const id = parseInt(req.params.id); const user = await this.userService.updateUser(id, req.body); res.json({ success: true, data: user }); } catch (error) { next(error); } }
async deleteUser(req: Request, res: Response, next: NextFunction) { try { const id = parseInt(req.params.id); await this.userService.deleteUser(id); res.status(204).send(); } catch (error) { next(error); } }}Step 8: Set Up Express Application
Section titled “Step 8: Set Up Express Application”import express, { Request, Response, NextFunction } from 'express';import { AppDataSource, initializeDatabase } from './config/database';import { UserController } from './controllers/user.controller';import { UserService } from './services/user.service';import { UserRepository } from './repositories/user.repository';
async function bootstrap() { // Initialize database await initializeDatabase();
// Set up repositories, services, controllers const userRepository = new UserRepository(AppDataSource.getRepository('User')); const userService = new UserService(userRepository); const userController = new UserController(userService);
// Create Express app const app = express(); app.use(express.json());
// Routes app.get('/users', (req, res, next) => userController.getAllUsers(req, res, next)); app.get('/users/:id', (req, res, next) => userController.getUserById(req, res, next)); app.post('/users', (req, res, next) => userController.createUser(req, res, next)); app.put('/users/:id', (req, res, next) => userController.updateUser(req, res, next)); app.delete('/users/:id', (req, res, next) => userController.deleteUser(req, res, next));
// Error handling middleware app.use((err: Error, req: Request, res: Response, next: NextFunction) => { console.error(err.stack); res.status(500).json({ success: false, message: 'Internal server error' }); });
// Start server const port = process.env.PORT || 3000; app.listen(port, () => { console.log(`Server running on port ${port}`); });}
bootstrap();21.5 Dependency Injection Without NestJS
Section titled “21.5 Dependency Injection Without NestJS”For smaller applications, implement simple dependency injection:
class Container { private services: Map<string, any> = new Map();
register<T>(key: string, instance: T): void { this.services.set(key, instance); }
resolve<T>(key: string): T { const service = this.services.get(key); if (!service) { throw new Error(`Service ${key} not found`); } return service; }}
export const container = new Container();
// Usagecontainer.register('userRepository', new UserRepository(AppDataSource.getRepository('User')));container.register('userService', new UserService(container.resolve<UserRepository>('userRepository')));21.6 Summary
Section titled “21.6 Summary”This chapter covered:
- Application Architecture: Layered architecture patterns for TypeScript
- Project Structure: Feature-based and layer-based organization
- Complete Implementation: From database setup to HTTP controllers
- Dependency Injection: Simple DI without frameworks
The key principles:
- Separate concerns across layers
- Use repositories for data access
- Keep business logic in services
- Controllers should be thin
Next Chapter
Section titled “Next Chapter”Chapter 22: Service Layer Design
Last Updated: February 2026