Shortcuts
BlogJuly 10, 2025

Mohamed Elbarry
Backend Architecture Patterns
Backend architecture is the foundation of any web application. It determines how your application handles requests, processes data, and scales with growth. Monolithic architecture is often the right choice for:
  • Small to medium-sized applications
  • Teams with limited DevOps expertise
  • Applications with simple deployment requirements
  • Rapid prototyping and MVP development
Monolithic Structure
// Express.js Monolithic Structure
src/
├── controllers/     # Request handlers
├── services/       # Business logic
├── models/         # Data models
├── middleware/     # Custom middleware
├── routes/         # Route definitions
├── utils/          # Utility functions
└── app.ts         # Application entry point
Microservices are ideal for:
  • Large, complex applications
  • Teams that can work independently
  • Applications requiring different scaling patterns
  • Systems with diverse technology requirements
Microservice Example
// User Service
const app = express();
app.use(express.json());

const userRepository = new UserRepository();
const userService = new UserService(userRepository);
const userController = new UserController(userService);

app.get('/users/:id', userController.getUser);
app.post('/users', userController.createUser);
app.put('/users/:id', userController.updateUser);
app.delete('/users/:id', userController.deleteUser);

export default app;
Domain-Driven Design focuses on modeling the business domain and organizing code around business concepts.
Domain Model
// Domain Layer
export class User {
private constructor(
  private readonly id: UserId,
  private email: Email,
  private name: string,
  private status: UserStatus
) {}

static create(email: string, name: string): User {
return new User(
UserId.generate(),
new Email(email),
name,
UserStatus.ACTIVE
);
}

changeEmail(newEmail: string): void {
this.email = new Email(newEmail);
}

isActive(): boolean {
return this.status === UserStatus.ACTIVE;
}
}
Repository Pattern
// Repository Interface
interface UserRepository {
findById(id: string): Promise<User | null>;
findByEmail(email: string): Promise<User | null>;
save(user: User): Promise<void>;
delete(id: string): Promise<void>;
}

// Concrete Implementation
class PostgresUserRepository implements UserRepository {
constructor(private db: Database) {}

async findById(id: string): Promise<User | null> {
const row = await this.db.query(
'SELECT * FROM users WHERE id = $1',
[id]
);

  return row ? this.toDomain(row) : null;

}

private toDomain(row: any): User {
return new User(
new UserId(row.id),
new Email(row.email),
row.name,
row.status
);
}
}
CQRS Implementation
// Commands
class CreateUserCommand {
constructor(
  public email: string,
  public name: string
) {}
}

// Command Handler
class CreateUserCommandHandler {
constructor(
private userRepository: UserRepository,
private eventStore: EventStore
) {}

async handle(command: CreateUserCommand): Promise<void> {
const user = User.create(command.email, command.name);

  await this.userRepository.save(user);

  await this.eventStore.append(
    user.getId(),
    new UserCreatedEvent(user.getId(), user.getEmail())
  );

}
}
API Gateway
class ApiGateway {
constructor(
  private userService: UserService,
  private orderService: OrderService,
  private productService: ProductService
) {}

async handleRequest(request: Request): Promise<Response> {
const { method, path } = request;

  // Route to appropriate service
  if (path.startsWith('/api/users')) {
    return await this.userService.handleRequest(request);
  } else if (path.startsWith('/api/orders')) {
    return await this.orderService.handleRequest(request);
  } else if (path.startsWith('/api/products')) {
    return await this.productService.handleRequest(request);
  }

  return new Response('Not Found', { status: 404 });

}
}
  • Choose the Right Pattern - Start simple, refactor when needed
  • Error Handling - Implement proper error handling
  • Monitoring - Add health checks and metrics
  • Security - Implement proper authentication and authorization
  • Documentation - Document your APIs and architecture decisions
The best architecture is the one that solves your specific problems while remaining maintainable and understandable by your team.
Share this post: