# API Conventions HTTP API design standards for Negareh backend. Swagger documentation is available at `/docs` when the server is running. ## Base URL No global path prefix. Routes encode the audience and resource directly: ``` POST /public/auth/otp/request GET /admin/orders GET /public/orders/:id ``` ## Route naming ### Audience prefixes | Prefix | Description | |--------|-------------| | `public/` | Customer-facing endpoints | | `admin/` | Back-office endpoints | Auth endpoints embed the audience in the path: `public/auth/...`, `admin/auth/...`. ### HTTP methods | Method | Usage | |--------|-------| | `GET` | Read single resource or list | | `POST` | Create resource or action (e.g. OTP request) | | `PATCH` | Partial update | | `PUT` | Full replace (rare) | | `DELETE` | Soft or hard delete | ### Path style - Use **plural nouns** for collections: `/admin/orders`, `/public/tickets`. - Use **path parameters** for IDs: `/admin/orders/:id`. - Use **query parameters** for filtering, sorting, and pagination. - Nest related actions under the resource: `/admin/orders/:id/assign-designer`. Controllers use `@Controller()` with the full path on each method (no shared controller prefix), except where a module uses a short prefix like `@Controller('admin')`. ## Request format ### Headers | Header | Required | Description | |--------|----------|-------------| | `Content-Type: application/json` | Yes (JSON bodies) | Standard JSON requests | | `Authorization: Bearer ` | Protected routes | JWT access token | ### Validation All inputs are validated through DTO classes with `class-validator`. The global `ValidationPipe` has `transform: true`, so query strings are coerced to the correct types. Invalid input returns `400` with validation messages in the error envelope. ### File uploads Multipart uploads use `@fastify/multipart`. Upload endpoints live in the `uploader` module and return file metadata/URLs from S3. ## Response format ### Success (single resource) ```json { "statusCode": 200, "success": true, "data": { "id": "01HXYZ...", "phone": "09362532122" } } ``` ### Success (paginated list) Services return `PaginatedResult`; the interceptor wraps it: ```json { "statusCode": 200, "success": true, "data": [ { "id": "01HXYZ...", "status": "pending" } ], "meta": { "total": 42, "page": 1, "limit": 10, "totalPages": 5 } } ``` ### Pagination query parameters Common fields on list DTOs: | Param | Type | Default | Description | |-------|------|---------|-------------| | `page` | number | `1` | Page number (1-based) | | `limit` | number | `10` | Items per page | | `search` | string | — | Free-text search | | `orderBy` | string | `createdAt` | Sort field | | `order` | `asc` \| `desc` | `desc` | Sort direction | Some endpoints may use cursor-based pagination and return `nextCursor` at the top level. ### Error ```json { "statusCode": 400, "success": false, "error": { "message": ["Phone number is invalid"] } } ``` `message` is always an array of strings, even for a single error. ### HTTP status codes | Code | When | |------|------| | `200` | Successful GET, PATCH, DELETE | | `201` | Successful POST (create) — when explicitly set | | `400` | Validation failure, bad input | | `401` | Missing or invalid token | | `403` | Authenticated but insufficient permissions | | `404` | Resource not found | | `429` | Rate limit exceeded | | `500` | Unexpected server error | ## Swagger - Title: **Negareh API** - Path: `/docs` - Bearer auth scheme enabled — use the Authorize button to set JWT. - Controllers document endpoints with `@ApiTags`, `@ApiOperation`, `@ApiBody`, `@ApiBearerAuth`. - DTO fields use `@ApiProperty` for examples and descriptions. ## Controller pattern ```typescript @ApiTags('orders') @ApiBearerAuth() @Controller() export class OrderController { constructor(private readonly orderService: OrderService) {} // User route @Get('public/orders') @UseGuards(AuthGuard) @ApiOperation({ summary: 'Get all orders with pagination and filters' }) findAll(@Query() dto: FindOrdersDto, @UserId() userId: string) { return this.orderService.findOrdersAsUser(userId, dto); } // Admin route @Post('admin/orders') @UseGuards(AdminAuthGuard) @Permissions(PermissionEnum.CREATE_ORDER) @ApiOperation({ summary: 'Create order' }) create(@AdminId() adminId: string, @Body() body: CreateOrderAsAdminDto) { return this.orderService.createOrderAsAdmin(adminId, body); } } ``` ## Identifiers - Resource IDs are **ULIDs** (26-character strings). - Pass IDs as path or body fields; do not use integer IDs. ## Dates and times - Stored as `timestamptz` in UTC. - Serialized as ISO 8601 strings in JSON responses. - Token `expire` fields use Unix milliseconds. ## CORS CORS is enabled for all origins in development with credentials support: - Methods: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTIONS` - `credentials: true` Tighten `origin` in production deployments. ## Rate limiting Global default: **5 requests per 60 seconds** per IP (`ThrottlerModule`). Stricter limits on sensitive routes: - OTP request: 3 per 180 seconds - Refresh token: custom decorator (`@RefreshTokenRateLimit()`) Exceeded limits return `429 Too Many Requests`. ## Versioning The API is currently unversioned (`v1` is implicit). Breaking changes should be coordinated with all clients (admin console, mobile app) before deployment. ## Adding a new endpoint checklist 1. Create or extend a DTO with validation and Swagger decorators. 2. Add a thin controller method with correct `public/` or `admin/` path. 3. Apply the appropriate guard and `@Permissions` if admin-only. 4. Implement logic in the service; use repository for queries. 5. Return typed data — the response interceptor handles wrapping. 6. Document with `@ApiOperation` and related Swagger decorators. 7. Add migration if new entities or columns are required.