5.4 KiB
Architecture
Negareh API is a NestJS backend built on Fastify, PostgreSQL (via MikroORM), and Redis (caching, OTP, queues). It powers both the customer-facing app and the admin console.
Tech stack
| Layer | Technology |
|---|---|
| Runtime | Node.js, TypeScript |
| Framework | NestJS 11 |
| HTTP server | Fastify (@nestjs/platform-fastify) |
| ORM | MikroORM 6 (PostgreSQL driver) |
| Auth | JWT + refresh tokens, OTP via SMS |
| Cache / OTP | Redis (@keyv/redis, CacheService) |
| Jobs | BullMQ (@nestjs/bullmq) |
| Real-time | Socket.IO (notifications) |
| File storage | AWS S3-compatible bucket |
| Payments | Zarinpal gateway |
| API docs | Swagger at /docs |
High-level layout
src/
├── main.ts # Bootstrap: Fastify, pipes, interceptors, CORS, Swagger
├── app.module.ts # Root module — wires all feature modules
├── config/ # MikroORM, cache, HTTP, Swagger
├── core/ # Cross-cutting: exceptions, interceptors, middlewares
├── common/ # Shared entities, decorators, enums, interfaces
├── modules/ # Feature modules (domain boundaries)
└── seeders/ # Database seed data
database/
└── migrations/ # Versioned schema changes (MikroORM migrations)
Feature modules
Each domain lives under src/modules/<name>/ with a consistent internal structure:
modules/<feature>/
├── <feature>.module.ts
├── controllers/ # HTTP layer (thin)
├── providers/ # Business logic (services)
├── repositories/ # Data access (extends EntityRepository)
├── entities/ # MikroORM entities
├── dto/ # Request/response validation
├── listeners/ # Event handlers (@OnEvent)
├── events/ # Domain events (EventEmitter2)
└── enum/ # Feature-specific enums
Current modules:
- auth — OTP login, JWT, refresh tokens
- user — End-user accounts and credit
- admin — Admin accounts
- roles — Roles and permissions (RBAC)
- product — Products and categories
- form-builder — Dynamic product fields and options
- request — Customer requests / quotes
- invoice — Invoicing
- order — Order lifecycle
- payment — Online payments (Zarinpal)
- print-form — Print form sections and PDF generation
- ticket — Support tickets
- chat — Order/chat messaging
- notification — SMS, push, WebSocket notifications
- uploader — File uploads to S3
- announcements, learnings, criticisms — Content and feedback
- util — Shared utilities (cache, phone normalization)
Request lifecycle
- Fastify receives the HTTP request.
- ValidationPipe validates and transforms DTOs (
class-validator+class-transformer). - Guards enforce authentication (
AuthGuard,AdminAuthGuard) and permissions (@Permissions). - Controller delegates to a service.
- Service uses repositories and/or
EntityManagerfor persistence. - ResponseInterceptor wraps successful responses in a standard envelope.
- HttpExceptionFilter formats error responses.
Cross-cutting concerns
Response envelope
Successful responses:
{
"statusCode": 200,
"success": true,
"data": { }
}
Paginated responses also include meta:
{
"statusCode": 200,
"success": true,
"data": [],
"meta": { "total": 100, "page": 1, "limit": 10, "totalPages": 10 }
}
Errors:
{
"statusCode": 400,
"success": false,
"error": { "message": ["..."] }
}
Domain events
Services emit events via EventEmitter2. Listeners in listeners/ handle side effects (notifications, chat, payment follow-up) without bloating the main flow.
Configuration
Use ConfigService for all environment variables. ConfigModule is registered globally in AppModule. Never read process.env directly in application code.
Rate limiting
@nestjs/throttler is configured globally (5 requests per 60 seconds). Sensitive endpoints (OTP, refresh token) use stricter per-route limits via @Throttle or custom decorators.
Design principles
- Thin controllers, fat services — HTTP concerns stay in controllers; business rules live in services.
- Reuse before rewrite — Search for existing services, DTOs, and repositories before adding new ones.
- Module boundaries — Features communicate through exported services or domain events, not by reaching into another module's internals.
- No duplicated logic — Extend existing modules rather than copying patterns.
- Transactions for multi-entity writes — Use
em.transactional()when several rows must commit or roll back together.
External integrations
| Integration | Purpose |
|---|---|
| SMS.ir | OTP and transactional SMS |
| Zarinpal | Payment gateway |
| S3-compatible storage | File uploads |
| Redis | OTP cache, general caching, BullMQ |
Local development
npm install
npm run start:dev # Watch mode on APP_PORT (default 4000)
Swagger UI: http://localhost:4000/docs
Database helpers (see database.md):
npm run migration:up # Apply pending migrations
npm run db:seed # Run seeders
npm run db:reset # Drop, create, migrate, seed (destructive)