|
1 | 1 | # YieldVault Backend API |
2 | 2 |
|
3 | | -Express.js backend server for YieldVault Stellar RWA platform with rate limiting and health monitoring. |
| 3 | +Express.js backend for the YieldVault Stellar RWA platform. |
4 | 4 |
|
5 | 5 | ## Features |
6 | 6 |
|
7 | | -- **Health Check Endpoint** (`/health`) - Real-time service health status |
8 | | -- **Readiness Endpoint** (`/ready`) - Dependency status for deployment orchestration |
9 | | -- **Rate Limiting** - Per-IP and per-API-key rate limiting to prevent abuse |
10 | | -- **Dependency Monitoring** - Checks for cache and Stellar RPC availability |
11 | | -- **Error Handling** - Consistent JSON error responses |
12 | | -- **TypeScript** - Full type safety with TypeScript |
| 7 | +- Health and readiness endpoints |
| 8 | +- Rate limiting for public API routes |
| 9 | +- Versioned API surface under `/api/v1` |
| 10 | +- Replay-safe mutations via idempotency keys |
| 11 | +- Background job retry policy and dead-letter metrics |
| 12 | +- Migration safety checks in CI |
| 13 | +- TypeScript with Jest test coverage |
13 | 14 |
|
14 | 15 | ## Quick Start |
15 | 16 |
|
16 | | -### Setup |
17 | | - |
18 | 17 | ```bash |
19 | | -# Install dependencies |
20 | 18 | npm install |
21 | | - |
22 | | -# Create environment file |
23 | 19 | cp .env.example .env |
24 | | -``` |
25 | | - |
26 | | -### Development |
27 | | - |
28 | | -```bash |
29 | | -# Start development server with auto-reload |
30 | 20 | npm run dev |
31 | 21 | ``` |
32 | 22 |
|
33 | | -The server will start on `http://localhost:3000`. |
34 | | - |
35 | | -### Production |
36 | | - |
37 | | -```bash |
38 | | -# Build TypeScript |
39 | | -npm run build |
40 | | - |
41 | | -# Start production server |
42 | | -npm start |
43 | | -``` |
| 23 | +The server starts on `http://localhost:3000`. |
44 | 24 |
|
45 | 25 | ## Configuration |
46 | 26 |
|
47 | | -Rate limiting and other settings are configurable via environment variables: |
48 | | - |
49 | 27 | | Variable | Default | Description | |
50 | | -|----------|---------|-------------| |
51 | | -| `PORT` | 3000 | Server port | |
52 | | -| `NODE_ENV` | development | Environment mode | |
53 | | -| `RATE_LIMIT_WINDOW_MS` | 900000 | Global rate limit window (15 min) | |
54 | | -| `RATE_LIMIT_MAX_REQUESTS` | 100 | Global requests per window | |
55 | | -| `API_RATE_LIMIT_WINDOW_MS` | 60000 | API rate limit window (1 min) | |
56 | | -| `API_RATE_LIMIT_MAX_REQUESTS` | 30 | API requests per window | |
57 | | -| `STELLAR_RPC_URL` | https://soroban-testnet.stellar.org | Stellar RPC endpoint | |
| 28 | +| --- | --- | --- | |
| 29 | +| `PORT` | `3000` | Server port | |
| 30 | +| `NODE_ENV` | `development` | Runtime mode | |
| 31 | +| `RATE_LIMIT_WINDOW_MS` | `900000` | Global rate limit window | |
| 32 | +| `RATE_LIMIT_MAX_REQUESTS` | `100` | Global requests per window | |
| 33 | +| `API_RATE_LIMIT_WINDOW_MS` | `60000` | API rate limit window | |
| 34 | +| `API_RATE_LIMIT_MAX_REQUESTS` | `30` | API requests per window | |
| 35 | +| `IDEMPOTENCY_KEY_TTL_MS` | `86400000` | Replay window for mutation requests | |
| 36 | +| `STELLAR_RPC_URL` | `https://soroban-testnet.stellar.org` | Stellar RPC endpoint | |
58 | 37 |
|
59 | 38 | ## API Endpoints |
60 | 39 |
|
61 | | -### Health Check |
| 40 | +### Health |
62 | 41 |
|
63 | | -``` |
| 42 | +```http |
64 | 43 | GET /health |
65 | | -``` |
66 | | - |
67 | | -Returns service health status with dependency checks. |
68 | | - |
69 | | -**Response (200 OK):** |
70 | | -```json |
71 | | -{ |
72 | | - "status": "healthy", |
73 | | - "timestamp": "2026-03-26T10:30:00.000Z", |
74 | | - "uptime": 3600.5, |
75 | | - "environment": "development", |
76 | | - "checks": { |
77 | | - "api": "up", |
78 | | - "cache": "up", |
79 | | - "stellarRpc": "up" |
80 | | - } |
81 | | -} |
82 | | -``` |
83 | | - |
84 | | -### Readiness Check |
85 | | - |
86 | | -``` |
87 | 44 | GET /ready |
88 | 45 | ``` |
89 | 46 |
|
90 | | -Returns service readiness state. Checks all critical dependencies before reporting ready. |
91 | | - |
92 | | -**Response (200 OK - Ready):** |
93 | | -```json |
94 | | -{ |
95 | | - "ready": true, |
96 | | - "timestamp": "2026-03-26T10:30:00.000Z", |
97 | | - "dependencies": { |
98 | | - "cache": true, |
99 | | - "stellarRpc": true |
100 | | - } |
101 | | -} |
102 | | -``` |
| 47 | +### Versioned API |
103 | 48 |
|
104 | | -**Response (503 Unavailable - Not Ready):** |
105 | | -```json |
106 | | -{ |
107 | | - "ready": false, |
108 | | - "timestamp": "2026-03-26T10:30:00.000Z", |
109 | | - "dependencies": { |
110 | | - "cache": false, |
111 | | - "stellarRpc": false |
112 | | - } |
113 | | -} |
| 49 | +```http |
| 50 | +GET /api/v1/vault/summary |
| 51 | +GET /api/v1/transactions |
| 52 | +GET /api/v1/portfolio/holdings |
| 53 | +GET /api/v1/vault/history |
| 54 | +POST /api/v1/vault/deposits |
| 55 | +GET /api/v1/ops/job-metrics |
114 | 56 | ``` |
115 | 57 |
|
116 | | -### Rate Limit Exceeded |
| 58 | +Legacy `/api/*` routes redirect to `/api/v1/*` with `308 Permanent Redirect`. |
117 | 59 |
|
118 | | -``` |
119 | | -Status: 429 Too Many Requests |
120 | | -``` |
121 | | - |
122 | | -```json |
123 | | -{ |
124 | | - "error": "Too many requests", |
125 | | - "status": 429, |
126 | | - "message": "Rate limit exceeded. Please try again later.", |
127 | | - "retryAfter": 1711432200000 |
128 | | -} |
129 | | -``` |
130 | | - |
131 | | -## Rate Limiting |
| 60 | +## Idempotent Mutations |
132 | 61 |
|
133 | | -### Global Rate Limiting |
| 62 | +Mutation endpoints require `x-idempotency-key`. |
134 | 63 |
|
135 | | -Applied to all requests except `/health` and `/ready`: |
136 | | -- Window: 15 minutes (configurable) |
137 | | -- Max: 100 requests per window (configurable) |
138 | | -- Per: IP address |
| 64 | +- Same key + same payload returns the original response. |
| 65 | +- Same key + different payload returns `409 Conflict`. |
| 66 | +- TTL is controlled by `IDEMPOTENCY_KEY_TTL_MS`. |
139 | 67 |
|
140 | | -### API Endpoint Rate Limiting |
141 | | - |
142 | | -Stricter limits for API endpoints (e.g., `/api/vault/summary`): |
143 | | -- Window: 1 minute (configurable) |
144 | | -- Max: 30 requests per window (configurable) |
145 | | -- Per: API key (from `x-api-key` header) or IP address |
146 | | - |
147 | | -## Testing |
148 | | - |
149 | | -```bash |
150 | | -# Run all tests |
151 | | -npm test |
| 68 | +## Background Jobs |
152 | 69 |
|
153 | | -# Run tests in watch mode |
154 | | -npm test -- --watch |
| 70 | +Job execution uses explicit retry policies per job class with exponential backoff. |
| 71 | +Failed jobs are written to a dead-letter sink and surfaced through job metrics. |
155 | 72 |
|
156 | | -# Run with coverage |
157 | | -npm test -- --coverage |
158 | | -``` |
| 73 | +## Migration Safety |
159 | 74 |
|
160 | | -## Issues Addressed |
| 75 | +The CI scanner flags: |
161 | 76 |
|
162 | | -### Issue #145: Rate Limiting |
163 | | -- ✅ Global rate limiting per IP |
164 | | -- ✅ Per-user/API-key rate limiting |
165 | | -- ✅ Configurable via environment variables |
166 | | -- ✅ Clear 429 responses with retry information |
167 | | -- ✅ Tests included for rate limiting behavior |
| 77 | +- irreversible schema changes such as `DROP` and `TRUNCATE` |
| 78 | +- long-running or locking migration patterns |
| 79 | +- schema changes that add indexed columns without an index declaration |
168 | 80 |
|
169 | | -### Issue #148: Health & Readiness Endpoints |
170 | | -- ✅ `/health` endpoint for service health |
171 | | -- ✅ `/ready` endpoint for deployment readiness |
172 | | -- ✅ Dependency health checks (cache, RPC) |
173 | | -- ✅ CI smoke test setup via npm scripts |
174 | | -- ✅ Consistent response formats |
| 81 | +Rollback expectations: |
175 | 82 |
|
176 | | -## CI/CD Integration |
| 83 | +- Prefer additive schema changes. |
| 84 | +- Add indexes before deploying code that depends on them. |
| 85 | +- Avoid destructive drops in the same deploy as a data migration. |
| 86 | +- Document manual rollback steps when a migration cannot be reversed automatically. |
177 | 87 |
|
178 | | -### Smoke Test (CI Pipeline) |
| 88 | +Run the scanner locally with: |
179 | 89 |
|
180 | 90 | ```bash |
181 | | -# Build and start server |
182 | | -npm run test:smoke |
183 | | - |
184 | | -# The server will start in background, ready for health checks |
185 | | -# Call: curl http://localhost:3000/health |
186 | | -# Call: curl http://localhost:3000/ready |
| 91 | +npm run check:migrations |
187 | 92 | ``` |
188 | 93 |
|
189 | | -### Docker Deployment |
190 | | - |
191 | | -Example Dockerfile: |
| 94 | +## Testing |
192 | 95 |
|
193 | | -```dockerfile |
194 | | -FROM node:20-alpine |
195 | | -WORKDIR /app |
196 | | -COPY package*.json ./ |
197 | | -RUN npm ci --only=production |
198 | | -COPY dist ./dist |
199 | | -EXPOSE 3000 |
200 | | -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ |
201 | | - CMD node -e "require('http').get('http://localhost:3000/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))" |
202 | | -CMD ["npm", "start"] |
| 96 | +```bash |
| 97 | +npm test |
| 98 | +npm run build |
| 99 | +npm run check:migrations |
203 | 100 | ``` |
204 | 101 |
|
205 | | -## Monitoring |
| 102 | +## CI |
206 | 103 |
|
207 | | -Headers returned in responses: |
| 104 | +The backend governance workflow runs: |
208 | 105 |
|
209 | | -- `RateLimit-Limit` - Request limit |
210 | | -- `RateLimit-Remaining` - Requests remaining |
211 | | -- `RateLimit-Reset` - Reset timestamp |
| 106 | +- `npm run lint` |
| 107 | +- `npm run test` |
| 108 | +- `npm run build` |
| 109 | +- `npm run check:migrations` |
212 | 110 |
|
213 | | -Example: |
214 | | -``` |
215 | | -RateLimit-Limit: 100 |
216 | | -RateLimit-Remaining: 95 |
217 | | -RateLimit-Reset: 1711432200 |
218 | | -``` |
| 111 | +See [`.github/workflows/backend-governance.yml`](../.github/workflows/backend-governance.yml). |
219 | 112 |
|
220 | 113 | ## License |
221 | 114 |
|
222 | | -MIT |
| 115 | +MIT |
0 commit comments