# Architecture

Author: Nguyen Thi Nguyet Que Created on: May 2, 2026

## Overview

```
Request → Middleware → Controller → Service (optional) → Provider → Database
                                         ↓
                                   External APIs
```

Controllers are thin HTTP adapters. Business logic lives in Services. Data access lives in
Providers.

---

## Layers

### Controller

**Responsibilities:** Parse request → call Service or Provider → format response.

**Must not:** contain business logic, transactions, or external API calls.

```typescript
handler: async (req: Req, res: Res) => {
  try {
    const result = await ProductService.createWithInventory(req.body);
    return res.sendOk({ data: result });
  } catch (error) {
    return res.error(error);
  }
},
```

### Service

**Responsibilities:** Business logic, multi-table transactions, external API calls, multi-platform
handling.

**Must not:** import Express types (`req`, `res`) — Services are HTTP-agnostic.

```typescript
export class ProductService {
	static async createWithInventory(data: CreateProductDTO) {
		return sequelize.transaction(async (t) => {
			const product = await Product.create(data, { transaction: t });
			await Inventory.create({ product_id: product.id }, { transaction: t });
			return product;
		});
	}
}
```

### Provider

**Responsibilities:** CRUD operations, queries, pagination/filter on a single model.

**Must not:** contain business logic, call external APIs, or coordinate across models.

```typescript
export class ProductProvider extends BaseProvider<Product> {
	constructor() {
		super('Product');
	}

	// Add complex queries as methods here
	async findByPriceRange(min: number, max: number) {
		return Product.findAll({ where: { price: { [Op.between]: [min, max] } } });
	}
}
```

---

## Decision guide

| Scenario                           | Use        |
| ---------------------------------- | ---------- |
| Simple CRUD on a single table      | Provider   |
| Transaction across multiple tables | Service    |
| External API call                  | Service    |
| Complex business logic             | Service    |
| Multi-platform logic (web/mobile)  | Service    |
| Query / filter / pagination        | Provider   |
| Response formatting                | Controller |

---

## Response format

All responses follow this structure:

```json
{
	"success": true,
	"responseData": {},
	"message": "Success",
	"message_en": "Success",
	"trace_id": "uuid-...",
	"timeStamp": "2026-05-01 10:00:00",
	"violations": null
}
```

In controllers:

```typescript
// Success
res.sendOk({ data: result, message: 'Created', message_en: 'Created' });

// Error — always pass Error or GenericError, never a plain object
res.error(error);
```

Access in client:

```typescript
if (response.success === true) {
	const item = response.responseData; // your data
}
// Validation errors: response.violations[] — array of { field, message }
```

---

## Error handling

Use `GenericError` for all domain errors:

```typescript
import { GenericError } from '#interfaces/error/generic';

// Factory method (preferred)
throw GenericError.create({ vi: 'Không tìm thấy', en: 'Not found' }, 'NOT_FOUND', 404);

// With additional data
throw GenericError.create(
	{ vi: 'Dữ liệu không hợp lệ', en: 'Invalid input' },
	'VALIDATION_ERROR',
	400,
	{ errors: [{ field: 'email', message: 'Invalid email' }] },
);
```

**Common error codes:** `NOT_FOUND` · `UNAUTHORIZED` · `FORBIDDEN` · `VALIDATION_ERROR` · `CONFLICT`
· `TOO_MANY_REQUESTS` · `INTERNAL_ERROR`

---

## Routing

Controllers under `src/controllers/api/v1/` are mounted at `/api/v1/`.

| File                                           | HTTP   | Route                         |
| ---------------------------------------------- | ------ | ----------------------------- |
| `api/v1/product/index.ts` → `get`              | GET    | `/api/v1/product`             |
| `api/v1/product/index.ts` → `post`             | POST   | `/api/v1/product`             |
| `api/v1/product/{id}.ts` → `get`               | GET    | `/api/v1/product/:id`         |
| `api/v1/product/{id}.ts` → `put`               | PUT    | `/api/v1/product/:id`         |
| `api/v1/product/{id}.ts` → `delete`            | DELETE | `/api/v1/product/:id`         |
| `api/v1/product/{id}/publish/index.ts` → `put` | PUT    | `/api/v1/product/:id/publish` |

Folder names use **singular kebab-case**: `user`, `role`, `file`, `role-permission`.

---

## Existing modules

```
src/controllers/api/v1/
├── auth/             # Login, register, refresh, logout
├── user/             # CRUD with role management
├── role/             # RBAC role management
├── permission/       # Permission definitions
├── role-permission/  # Role ↔ Permission mapping
├── user-role/        # User ↔ Role assignment
├── file/             # File upload and storage
└── notifications/    # Push, email, in-app notifications
```

Each module has: Controller · Service · Provider · Validator

---

## Common middleware

| Middleware                   | Purpose                       | Import path                   |
| ---------------------------- | ----------------------------- | ----------------------------- |
| `verify`                     | JWT authentication            | `#middlewares/auth`           |
| `detectPlatform`             | Detect web / mobile / zalo    | `#middlewares/platform`       |
| `queryModifier`              | Parse pagination/filter query | `#middlewares/query-modifier` |
| `createDistributedRateLimit` | Redis-backed rate limiting    | `#middlewares/rateLimiter`    |
| `validateId`                 | Validate UUID `req.params.id` | `#middlewares/validators`     |
