9.6 KiB
9.6 KiB
API Request Flow Documentation
GET /users/:userId Request Flow
Complete Request Journey
Mobile App (Android/iOS)
↓
GET http://10.0.2.2:3200/users/:userId
Headers: Authorization: Bearer <JWT_TOKEN>
↓
┌─────────────────────────────────────────────────────────────┐
│ Backend Server (Port 3200) │
│ │
│ 1. Express App Layer │
│ ├─ CORS middleware │
│ ├─ express.json() │
│ │ │
│ 2. Global Middleware Chain │
│ ├─ requestContext │
│ │ └─ Extracts: IP, User-Agent, Request-ID │
│ │ │
│ ├─ auditLoggerMiddleware │
│ │ └─ Attaches audit logger to req.auditLogger │
│ │ │
│ 3. Route Matching │
│ └─ /users → userRoutes │
│ │
│ 4. Route-Level Middleware (userRoutes.js) │
│ ├─ jwtAuthenticate │
│ │ ├─ Extracts token from Authorization header │
│ │ ├─ Calls Auth Service API: │
│ │ │ POST http://auth-service:3000/auth/validate-token
│ │ │ Body: { token: <JWT_TOKEN> } │
│ │ ├─ Auth Service validates: signature, expiry, etc. │
│ │ └─ Sets req.user = { userId, role, ... } │
│ │ │
│ ├─ rateLimiterRead │
│ │ └─ Checks rate limits (Redis-based) │
│ │ │
│ ├─ requireUserOrAdmin (coarseAuthorize) │
│ │ └─ Verifies role is USER or ADMIN │
│ │ │
│ └─ fineAuthorize (for GET /:id only) │
│ └─ Verifies user can read this specific user │
│ (user can read own profile OR admin can read any)│
│ │
│ 5. Route Handler │
│ └─ GET /:id handler │
│ ├─ Extracts userId from req.params.id │
│ ├─ Database Query (via queryHelper) │
│ │ SELECT * FROM users WHERE id = :userId │
│ └─ Returns user data │
└─────────────────────────────────────────────────────────────┘
↓
Response: { id, name, phone_number, avatar_url, ... }
↓
Mobile App
Services & Dependencies
-
Backend Server (Port 3200)
- Main API server
- Handles business logic
-
Auth Service (Port 3000)
- JWT token validation endpoint:
/auth/validate-token - Called by
jwtAuthenticatemiddleware - Returns user payload if token is valid
- JWT token validation endpoint:
-
Database (PostgreSQL)
- Stores user data
- Queried via queryHelper
-
Redis (Optional)
- Used for rate limiting
- Falls back gracefully if unavailable
Implementation Pattern for All APIs
Standard Middleware Chain Pattern
All API routes should follow this consistent pattern:
// routes/exampleRoutes.js
import express from "express";
import jwtAuthenticate from "../middleware/jwtAuthenticate.js";
import { rateLimiterRead, rateLimiterWrite } from "../middleware/rateLimiter.js";
import createCoarseAuthorize from "../middleware/coarseAuthorize.js";
import { fineAuthorize } from "../middleware/fineAuthorize.js";
const router = express.Router();
// 1. Apply authentication to ALL routes
router.use(jwtAuthenticate);
// 2. Apply rate limiting (read for GET, write for POST/PUT/DELETE)
router.use(rateLimiterRead); // Default: read rate limiter
// 3. Apply coarse-grained authorization (role-based)
const requireUserOrAdmin = createCoarseAuthorize(['USER', 'ADMIN']);
router.use(requireUserOrAdmin);
// 4. Define routes with fine-grained authorization where needed
router.get("/", async (req, res) => {
// No fine authorization - all authenticated users can list
});
router.get("/:id",
fineAuthorize({
action: 'read',
resource: 'resource_type', // e.g., 'user', 'order', 'listing'
getResourceOwnerId: (req) => req.params.id,
}),
async (req, res) => {
// Handler logic
}
);
router.post("/",
rateLimiterWrite, // Override default rate limiter for write operations
async (req, res) => {
// Handler logic
}
);
router.put("/:id",
rateLimiterWrite,
fineAuthorize({
action: 'write',
resource: 'resource_type',
getResourceOwnerId: (req) => req.params.id,
}),
async (req, res) => {
// Handler logic
}
);
Middleware Execution Order
Request
↓
[Global Middleware - server.js]
1. requestContext
2. auditLoggerMiddleware
3. CORS
4. express.json()
↓
[Route Matching]
↓
[Route-Level Middleware - routes/*.js]
1. jwtAuthenticate → Validates JWT, sets req.user
2. rateLimiterRead/Write → Rate limiting
3. coarseAuthorize → Role-based access (USER/ADMIN)
4. fineAuthorize (optional) → Resource-level permissions
↓
[Route Handler]
↓
Response
Quick Reference: Route Setup Checklist
For each new API route file:
- Import required middleware
- Apply
jwtAuthenticateto all routes (router.use) - Apply appropriate rate limiter (router.use or per-route)
- Apply coarse authorization (router.use)
- Add fine authorization for resource-specific routes
- Add audit logging in handlers (req.auditLogger)
- Handle errors appropriately
Example: New Resource Route
// routes/productsRoutes.js
import express from "express";
import jwtAuthenticate from "../middleware/jwtAuthenticate.js";
import { rateLimiterRead, rateLimiterWrite } from "../middleware/rateLimiter.js";
import createCoarseAuthorize from "../middleware/coarseAuthorize.js";
import { fineAuthorize } from "../middleware/fineAuthorize.js";
import { select, insert, update } from "../db/queryHelper/index.js";
const router = express.Router();
// Authentication & Authorization
router.use(jwtAuthenticate);
router.use(rateLimiterRead);
router.use(createCoarseAuthorize(['USER', 'ADMIN']));
// GET /products - List all (public listings, no fine auth needed)
router.get("/", async (req, res) => {
try {
const products = await select({ table: 'products', where: { deleted: false } });
res.json(products);
} catch (err) {
res.status(500).json({ error: "Internal server error" });
}
});
// GET /products/:id - Get specific product
router.get("/:id",
fineAuthorize({
action: 'read',
resource: 'product',
getResourceOwnerId: (req) => req.params.id,
}),
async (req, res) => {
try {
const product = await select({
table: 'products',
where: { id: req.params.id, deleted: false },
limit: 1,
});
if (product.length === 0) return res.status(404).json({ error: "Not found" });
res.json(product[0]);
} catch (err) {
res.status(500).json({ error: "Internal server error" });
}
}
);
// POST /products - Create product (requires write rate limiter)
router.post("/",
rateLimiterWrite,
async (req, res) => {
try {
const product = await insert({
table: 'products',
data: req.body,
returning: '*',
});
res.status(201).json(product);
} catch (err) {
res.status(500).json({ error: "Internal server error" });
}
}
);
// PUT /products/:id - Update product
router.put("/:id",
rateLimiterWrite,
fineAuthorize({
action: 'write',
resource: 'product',
getResourceOwnerId: (req) => req.params.id,
}),
async (req, res) => {
try {
const updated = await update({
table: 'products',
data: req.body,
where: { id: req.params.id, deleted: false },
returning: '*',
});
if (updated.length === 0) return res.status(404).json({ error: "Not found" });
res.json(updated[0]);
} catch (err) {
res.status(500).json({ error: "Internal server error" });
}
}
);
export default router;
Summary
All API requests follow this pattern:
- Mobile App sends request with JWT token
- Backend receives request → Global middleware → Route middleware → Handler
- JWT validated via Auth Service
- Rate limiting applied
- Authorization checks (coarse + fine)
- Business logic executes
- Response returned to app
Key Principles:
- JWT authentication is mandatory for all protected routes
- Rate limiting prevents abuse
- Coarse authorization checks roles (USER/ADMIN)
- Fine authorization checks resource-level permissions
- Audit logging tracks all operations