🕵️ SicherheitslückenHak5: Hackers Just Poisoned the Rust Supply Chain | Threat Wire(01.09.2026 um 14:00 Uhr)
🕵️ SicherheitslückenHak5: Hackers Found a Way Into Humanoid Robots | Threat Wire(04.09.2026 um 15:04 Uhr)
🔧 AI Nachrichten Bits und so #1021 (Passwort für Laufwerk)(31.08.2026 um 22:15 Uhr)
🔧 AI Nachrichten Bits und so #1022 (Wie Weißbier)(06.09.2026 um 20:39 Uhr)
🍏 iOS / Mac OSHue-App 6.0 ist da: das sind die Neuerungen(07.09.2026 um 17:21 Uhr)
🕵️ SicherheitslückenHak5: Hackers Just Poisoned the Rust Supply Chain | Threat Wire(01.09.2026 um 14:00 Uhr)
🕵️ SicherheitslückenHak5: Hackers Found a Way Into Humanoid Robots | Threat Wire(04.09.2026 um 15:04 Uhr)
🔧 AI Nachrichten Bits und so #1021 (Passwort für Laufwerk)(31.08.2026 um 22:15 Uhr)
🔧 AI Nachrichten Bits und so #1022 (Wie Weißbier)(06.09.2026 um 20:39 Uhr)
🍏 iOS / Mac OSHue-App 6.0 ist da: das sind die Neuerungen(07.09.2026 um 17:21 Uhr)

🔧 Programmierung 🕛 kürzlich 7 Min Lesezeit
0

REST API Design Best Practices: A Practical Guide for 2026

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

Every team builds APIs. Few build ones that survive their second rewrite. After inheriting three different REST APIs in as many years — one with endpoints named /getAllUsers, another that returned { "status": "ok" } for both success and 500 errors — I started keeping a list of the practices that actually distinguish robust APIs from ones that generate PagerDuty alerts at 3 AM.



This guide distills six rules I've validated across production services handling tens of millions of requests. None are theoretical. All come with working code.






1. Resource-Oriented Naming — Not Action-Oriented



The single biggest smell in a REST API is action verbs in URLs:




CODE
# Bad — these are RPC, not REST
GET /api/getUser?id=42
POST /api/createUser
POST /api/deleteUser/42
POST /api/activateUserSubscription




Resources are nouns, not verbs. The HTTP method is the verb:




CODE
# Good
GET /api/users/42
POST /api/users
DELETE /api/users/42
POST /api/users/42/subscriptions # nested resource
DELETE /api/users/42/subscriptions/active




Key conventions that have held across every production API I've consulted on:





  • Plural nouns : /users, not /user. Consistency with list endpoints (GET /users = a list) makes singular feel like a bug.


  • Kebab-case for multi-word resources : /order-items, not /orderItems or /order_items. It's URL-safe and matches what browsers expect.


  • Nest at most two levels : /users/42/orders/7 is fine. /users/42/orders/7/items/3/addresses/9 is a cry for help. At that point, use a query parameter: /items?order_id=7.


  • Use query params for filtering, not path segments : /users?status=active&role=admin, not /users/active/admins.






2. Consistent Error Responses — The Contract People Actually Rely On



Most API errors are parsable only by humans staring at a screen. That's a bug. Every error response should follow the same schema so clients can handle them programmatically:




CODE
{
"error": {
"code": "USER_NOT_FOUND",
"message": "User with id 42 was not found.",
"details": {
"resource": "users",
"identifier": "42"
},
"request_id": "req_a1b2c3d4"
}
}




The code field is the key — it's a machine-readable string clients can switch on. Never change the meaning of a code after release. The request_id is a trace identifier your logs and your users' bug reports can share, so debugging a production error doesn't start with "which request was that."



Here's a FastAPI middleware that enforces this consistently:




CODE
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

class APIException(Exception):
def __init__(self, code: str, message: str, status: int, details: dict = None):
self.code = code
self.message = message
self.status = status
self.details = details or {}

@app.exception_handler(APIException)
async def api_error_handler(request: Request, exc: APIException):
return JSONResponse(
status_code=exc.status,
content={
"error": {
"code": exc.code,
"message": exc.message,
"details": exc.details,
"request_id": request.headers.get("x-request-id", "unknown"),
}
},
)




With this in place, your HTTP status codes still mean what HTTP says they mean (4xx vs 5xx), but the code field gives clients an actionable discriminator. Stripe pioneered this pattern and it's stood the test of a decade.






3. Pagination That Doesn't Surprise



Cursor-based pagination beats offset-based for production APIs. Here's why: if a new record is inserted before page 2, offset pagination shifts every subsequent page — users see duplicates or miss records. Cursor pagination uses a stable pointer:




CODE
GET /users?cursor=eyJpZCI6IDQyfQ==&limit=20

Response:
{
"data": [ ... ],
"pagination": {
"next_cursor": "eyJpZCI6IDg0fQ==",
"has_more": true,
"limit": 20
}
}




The cursor is an opaque base64-encoded token, usually containing the last seen record's ID and a timestamp. Clients don't decode it — just pass it to the next request. Implementation is straightforward with SQL's WHERE id > :cursor ORDER BY id pattern:




CODE
SELECT id, name, email FROM users
WHERE id > :cursor
ORDER BY id ASC
LIMIT 21 -- fetch one extra to detect has_more




The extra row tells you has_more = rows_returned == limit + 1. Drop that row from the response and encode its id as the next cursor. This pattern handles millions of rows without the performance cliff that OFFSET hits past page 50.






4. API Versioning — Accept or Header, Not URL Path



The old orthodoxy was /api/v1/users. This commits you to maintaining parallel URL trees forever. A better approach: use the Accept header (content negotiation):




CODE
# Request
GET /api/users/42
Accept: application/vnd.devopsdaily.v2+json

# Response
Content-Type: application/vnd.devopsdaily.v2+json




Why this wins: the same URL always works. New clients get v2, old clients get v1, your infrastructure stays simple. When v3 ships, you don't redeploy — you update a default-version config. Stripe and GitHub both moved to this model for good reason.



The practical downside is discoverability — a developer can't glance at the URL and see "this is v2." Mitigate by documenting version defaults in your API reference and returning the version in response headers:




CODE
X-API-Version: 2
Deprecated: true
Sunset: Sat, 19 Dec 2026 00:00:00 GMT




If you absolutely must use URL versioning (perhaps your consumers are curl-in-a-shell-script types), stick to the simple /v1/ prefix — but deprecate it in favor of the Accept header on your next major release.






5. Rate Limiting That Tells You When to Retry



Rate limiting without feedback headers is just annoying. Every response — successful or not — should carry:




CODE
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 483
X-RateLimit-Reset: 1721414400




And when the client exceeds the limit:




CODE
HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/json

{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Retry after 42 seconds.",
"details": {
"limit": 1000,
"reset_at": "2026-07-19T14:00:00Z"
}
}
}




The Retry-After header is the most important part — without it, clients have to guess, and they'll either hammer you (making things worse) or back off too conservatively (starving your own metrics). If you're using a token bucket algorithm (and you should be), the reset time is simply the bucket's next refill tick.



Here's a minimal implementation using sliding window counters in Python — no external dependency:




CODE
import time
from collections import defaultdict

class SlidingWindowRateLimiter:
def __init__(self, max_requests: int, window_seconds: int = 60):
self.max_requests = max_requests
self.window = window_seconds
self.requests = defaultdict(list) # key -> [timestamps]

def check(self, key: str) -> tuple[bool, int, int]:
now = time.time()
cutoff = now - self.window
self.requests[key] = [t for t in self.requests[key] if t > cutoff]

remaining = self.max_requests - len(self.requests[key])
if remaining > 0:
self.requests[key].append(now)
return True, remaining, int(cutoff + self.window)
return False, 0, int(cutoff + self.window)




For production, swap this for Redis-backed counters — but the API contract stays identical.






6. Idempotency and Safe Retries



Network failures happen. When a client sends POST /api/payments and gets a timeout, was the payment charged or not? Without idempotency, the safe answer is "I don't know" — which is the wrong answer.



Use idempotency keys. The client generates a unique key (UUID) and sends it on every mutation request:




CODE
POST /api/payments
Idempotency-Key: 7c7a3e81-3a1e-4b1e-8e1e-3e1e7c7a3e81
Content-Type: application/json

{
"amount": 2999,
"currency": "usd",
"source": "tok_visa"
}




The server caches the response keyed by (Idempotency-Key, endpoint). If it sees the same key again, it returns the cached response instead of executing the operation twice:




CODE
# Fast enough for most use cases — a simple dict
# For production: Redis with TTL matching your max retry window
idempotency_store: dict[str, dict] = {}

@app.post("/api/payments")
async def create_payment(request: Request):
idem_key = request.headers.get("Idempotency-Key")
if not idem_key:
raise APIException("IDEMPOTENCY_KEY_REQUIRED", ...)

cached = idempotency_store.get(idem_key)
if cached:
return JSONResponse(content=cached, status_code=200)

result = await process_payment(request.json())
idempotency_store[idem_key] = result
return JSONResponse(content=result, status_code=201)




Stripe's idempotency layer handles billions of dollars. The pattern has been battle-tested. Copy it.






Putting It All Together



A well-designed REST API is a contract. The six practices above — resource naming, structured errors, cursor pagination, header-based versioning, informative rate limits, and idempotency keys — form the foundation of APIs that developers enjoy integrating with.



The next time you're building an endpoint, skip the framework boilerplate and start with these. Your consumers (and your on-call rotation) will thank you.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
Hackers Just Poisoned the Rust Supply Chain | Threat Wire
1 Quelle
Hackers Found a Way Into Humanoid Robots | Threat Wire
1 Quelle
Bits und so #1021 (Passwort für Laufwerk)
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten REST API Design Best Practices: A Practical Guide for 2026

Thematisch verwandte Begriffe: REST, Design, Best, Practices · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...