A teammate has drafted the API for an order service and asked for a review before the frontend team builds against it. This capstone walks through that review the way a senior engineer would: spot each violation of what you learned in this course, name the principle behind the fix, and rewrite the design, ending with a production checklist.
GET /getAllOrders?token=abc123 -> 200 [ ...every order in the database... ]
POST /createOrder -> 200 { "success": true, "orderId": 17 }
POST /deleteOrder?id=17 -> 200 { "success": false, "error": "not found" }
POST /orders/17/setStatus {"status":1} -> 500 "TypeError: Cannot read properties..."
GET /orders/17 -> 200 { ..., "customer": { "passwordHash": "..." } }| Finding | Principle | Fix |
|---|---|---|
| Verbs in URLs (/getAllOrders) | Resources are nouns; methods are the verbs | GET /v1/orders, DELETE /v1/orders/17 |
| Token in the query string | Credentials belong in headers; URLs get logged | Authorization: Bearer <token> |
| 200 with "success": false | Status codes carry the outcome | 404 with an application/problem+json body |
| Returns every order at once | Collections are paginated and filterable | ?status=&limit=&cursor= with links.next |
| Anyone can read any order | Object-level authorization (OWASP API1) | Filter by ownerId |
| passwordHash in the response | Property-level authorization (OWASP API3) | Explicit serializers; never dump ORM rows |
| Stack trace on bad input | Validate first; never leak internals | 422 with field errors; generic 500 |
| POST /setStatus action | State changes are updates to the resource | PATCH /v1/orders/17 { "status": "shipped" } |
The rewritten list endpoint applies the findings: scope check, ownership filter, cursor pagination, a next link, ETag revalidation, and errors forwarded to the Problem Details middleware:
router.get("/orders", requireScope("orders:read"), async (req, res, next) => {
try {
const { status, limit = 20, cursor } = req.query;
const page = await orders.list({ ownerId: req.auth.sub, status, limit: Math.min(Number(limit), 100), cursor });
const body = {
data: page.items,
links: { self: req.originalUrl, next: page.nextCursor ? `/v1/orders?limit=${limit}&cursor=${page.nextCursor}` : null },
};
const etag = etagFor(body);
res.set("ETag", etag);
if (req.get("If-None-Match") === etag) return res.status(304).end();
res.json(body);
} catch (err) {
next(err);
}
});{ "data": [{ "id": "ord_17", "status": "shipped", "total": 2499 }],
"links": { "next": "/v1/orders?limit=20&cursor=eyJpZCI6Im9yZF8xNyJ9" } }Design: nouns in URLs, correct methods and status codes, pagination on every collection, /v1 versioning, consistent naming.
Security, mapped to the OWASP API Security Top 10: ownership checks on every object (API1); short-lived tokens and PKCE (API2); explicit serializers and input whitelists (API3); rate, body-size and pagination limits (API4); role checks on admin functions (API5); idempotency keys on sensitive flows (API6); never fetch client-supplied URLs unvalidated (API7); HTTPS, strict CORS, no stack traces (API8); an inventory of every version (API9); validate third-party data like user input (API10).
Reliability and operability: idempotent retries, 202 for long jobs, outbound timeouts, a health endpoint, request ids in logs, metrics per endpoint, a published OpenAPI document, tests in CI.
| | REST | GraphQL | gRPC |
|---|---|---|---|
| Shape | Resources at URLs | Client-defined queries, one endpoint | Typed procedures over HTTP/2 |
| Contract | OpenAPI (optional) | Schema (required) | .proto files (required) |
| Caching | Native HTTP caching | Hard; mostly client-side | None built in |
| Best for | Public APIs, CRUD, browsers | UIs aggregating resources | Internal services, streaming |
REST is the default for public APIs; GraphQL suits flexible aggregation and gRPC high-volume internal traffic.
The draft returns `200 { "success": false }` for a missing order. What is the correct response?
application/problem+json, collections are paginated with links, and reads carry ETags.What to learn next: continue with the GraphQL course, explore gRPC for internal services, and study API gateways and observability tooling.