Aller au contenu principal

Architecture Overview

Express Base API follows a clean, modular MVC (Model-View-Controller) architecture with additional layers for separation of concerns.

Architecture Layers

┌─────────────────────────────────────┐
│ Client Application │
└──────────────┬──────────────────────┘
│ HTTP Requests
┌──────────────▼──────────────────────┐
│ Routes + Middleware │
│ (Authentication, Validation, │
│ Rate Limiting, Logging) │
└──────────────┬──────────────────────┘

┌──────────────▼──────────────────────┐
│ Controllers │
│ (Request handling, Response │
│ formatting) │
└──────────────┬──────────────────────┘

┌──────────────▼──────────────────────┐
│ Services │
│ (Business logic, Database │
│ operations, Caching) │
└──────────────┬──────────────────────┘

┌──────────────▼──────────────────────┐
│ Models (Mongoose) │
│ (Database schemas, │
│ Validation) │
└──────────────┬──────────────────────┘

┌──────────────▼──────────────────────┐
│ MongoDB Database │
└─────────────────────────────────────┘

Cache Layer (Redis)
┌─────────────────────────────────────┐
│ Caching Service │
│ - Authentication cache │
│ - Query result cache │
│ - Rate limiting │
└─────────────────────────────────────┘

Queue System (Bull)
┌─────────────────────────────────────┐
│ Background Jobs │
│ - Email sending │
│ - Data processing │
└─────────────────────────────────────┘

Directory Structure

express-backend-ts/
├── bin/ # CLI tools and scripts
│ ├── cli # Main CLI entry point
│ ├── script/ # Generation scripts
│ └── templates.js # Code templates
├── configs/ # Configuration files
│ ├── app.config.ts # App-wide configuration
│ ├── database.config.ts # MongoDB configuration
│ ├── redis.config.ts # Redis configuration
│ └── response.config.ts # Response formatting
├── docs/ # API documentation
│ ├── swagger.ts # Swagger configuration
│ └── routes/ # Route documentation
├── routes/ # Route definitions
│ ├── routes.ts # Main route registry
│ ├── admin/ # Admin routes
│ ├── app/ # Application routes
│ └── auth.route.ts # Authentication routes
├── src/ # Source code
│ ├── controllers/ # Request handlers
│ │ ├── admin/
│ │ ├── app/
│ │ └── auth.controller.ts
│ ├── models/ # Database models
│ │ ├── auth/
│ │ └── log.model.ts
│ ├── services/ # Business logic
│ │ ├── admin/
│ │ ├── app/
│ │ └── auth/
│ └── types/ # TypeScript interfaces
│ ├── service.types.ts
│ └── auth.types.ts
├── utils/ # Utilities and helpers
│ ├── bases/ # Base classes
│ │ ├── base.controller.ts
│ │ ├── base.service.ts
│ │ └── mail.service.ts
│ ├── helpers/ # Helper functions
│ │ ├── cache.helper.ts
│ │ └── logger.helper.ts
│ ├── interceptors/ # Request/response interceptors
│ ├── middlewares/ # Express middlewares
│ │ ├── app.middleware.ts
│ │ └── auth/
│ ├── seeders/ # Database seeders
│ └── validations/ # Joi validation schemas
├── templates/ # Email templates
│ └── auth/
├── main.ts # Application entry point
├── package.json
└── tsconfig.json

Request Flow

1. HTTP Request Arrives

GET /product/123
Headers:
x-api-key: xxx
Authorization: Bearer yyy

2. Middleware Stack

// Route definition in routes.ts
{
path: '/product',
middlewares: [
Middleware.xApiKey, // Validate API key
Middleware.rateLimit, // Check rate limits
Middleware.isLogin, // Authenticate user
]
}

Execution order:

  1. API Key Validation - Checks x-api-key header
  2. Rate Limiting - Prevents abuse (Redis-based)
  3. Authentication - Validates JWT token (cache-first)
  4. Request Logging - Logs request details

3. Controller Layer

// routes/app/product.route.ts
import ProductController from '../../src/controllers/app/product.controller';

const productController = new ProductController();
router.get('/:id', productController.show);

Controller responsibilities:

  • Extract request parameters
  • Call appropriate service method
  • Format response
  • Handle errors

4. Service Layer

// src/services/app/product.service.ts
class ProductService extends BaseService<IProduct> {
async findById(id: string) {
// Check cache first
const cached = await this.cacheGet(`product:${id}`);
if (cached) return { error: false, data: cached };

// Query database
const result = await this.model.findById(id);

// Cache result
await this.cacheSet(`product:${id}`, result, 3600);

return { error: false, data: result };
}
}

Service responsibilities:

  • Business logic implementation
  • Database operations
  • Caching
  • Data transformation
  • Transaction management

5. Model Layer

// src/models/product.model.ts
const productSchema = {
name: { type: String, required: true },
price: { type: Number, required: true },
stock: { type: Number, default: 0 },
};

const Product: Model<IProduct> = BaseSchema<IProduct>(
'products',
productSchema
);

Model responsibilities:

  • Schema definition
  • Validation rules
  • Indexes
  • Virtual properties
  • Static methods

6. Response

{
"status_code": 200,
"status": "SUCCESS",
"message": "Product retrieved successfully",
"data": {
"_id": "65abc123...",
"name": "Laptop",
"price": 999.99,
"stock": 10
}
}

Design Patterns

Singleton Pattern

Controllers and services use singleton pattern for optimal memory usage:

// Only one instance per service
class ProductService extends BaseService<IProduct> {
private static instance: ProductService;

static getInstance() {
if (!ProductService.instance) {
ProductService.instance = new ProductService(Product);
}
return ProductService.instance;
}
}

export default ProductService.getInstance();

Benefits:

  • Reduced memory footprint
  • Shared state across requests
  • Database connection pooling

Repository Pattern

BaseService acts as a repository with common CRUD operations:

class BaseService<T> {
async find(query, options?): Promise<QueryResult<T[]>>
async findById(id: string): Promise<QueryResult<T>>
async create(data): Promise<QueryResult<T>>
async update(query, data): Promise<QueryResult<T>>
async delete(query): Promise<QueryResult<T>>
}

Dependency Injection

Services are injected into controllers:

class ProductController extends BaseController {
constructor() {
super(productService);
}
}

Cache-Aside Pattern

Services use cache-aside for optimal performance:

// Check cache → Cache miss → Query DB → Cache result
const data = await this.cacheGetOrSet(
cacheKey,
async () => await this.find(query),
ttl
);

Key Principles

Separation of Concerns

Each layer has a single responsibility:

LayerResponsibilityShould NOT
RoutesDefine endpoints, apply middlewareContain business logic
ControllersHandle HTTP, format responsesAccess database directly
ServicesBusiness logic, database operationsParse HTTP requests
ModelsData schema, validationContain business logic

DRY (Don't Repeat Yourself)

  • BaseController - Common controller methods
  • BaseService - Common CRUD operations
  • Base schemas - Automatic timestamps, soft delete
  • Response formatting - Centralized response structure

Type Safety

Full TypeScript support with strict mode:

interface IProduct extends BaseDocument {
name: string;
price: number;
stock: number;
}

class ProductService extends BaseService<IProduct> {
// Full type inference and checking
}

Error Handling

Centralized error handling throughout the stack:

try {
const result = await service.create(data);
if (result.error) {
return response.error(res, result.message, 400);
}
return response.success(res, result.data, 201);
} catch (error) {
logger.error('Error creating product', error);
return response.error(res, 'Internal server error', 500);
}

Performance Optimizations

Caching Strategy

  1. Authentication Cache (1-hour TTL)

    • User + Auth data cached on login
    • Cache-first authentication middleware
  2. Query Cache (configurable TTL)

    • Frequently accessed data cached
    • Automatic invalidation on updates
  3. Rate Limit Cache (Redis)

    • Track request counts per IP/user
    • Sliding window algorithm

Database Optimizations

  1. Indexes - Critical fields indexed
  2. Pagination - Limit query results
  3. Projection - Select only needed fields
  4. Population - Efficient reference loading

Background Processing

Queue system for non-blocking operations:

// Email sending in background
await mailQueue.add('send-email', {
to: user.email,
template: 'activation',
data: { token: activationToken }
});

Scalability Considerations

Horizontal Scaling

  • Stateless design - No session storage in memory
  • Redis cache - Shared across instances
  • Bull queue - Distributed job processing
  • MongoDB - Replication and sharding support

Vertical Scaling

  • Connection pooling - Efficient database connections
  • Memory management - Singleton pattern
  • Async/await - Non-blocking operations
  • Stream processing - Large file handling

Security Architecture

Defense in Depth

Multiple security layers:

  1. API Key Validation - First line of defense
  2. Rate Limiting - Prevent abuse
  3. JWT Authentication - User verification
  4. Input Validation - Joi schemas
  5. SQL/NoSQL Injection - Mongoose sanitization
  6. XSS Protection - Helmet.js
  7. CORS - Origin control

Authentication Flow

1. Login → JWT Access Token (1h) + Refresh Token (7d)
2. Request → Middleware checks cache → Validate token
3. Token expired → Use refresh token → New access token
4. Refresh expired → Force re-login
5. Logout → Invalidate cache + delete refresh token

Next Steps


Questions? Check the GitHub Discussions or open an issue.