Files
negareh-api/docs/api-conventions.md
T
2026-07-02 15:59:46 +03:30

220 lines
5.8 KiB
Markdown

# 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 <token>` | 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<T>`; 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.