Top REST API Design Pitfalls by Victor Rentea

Abstract

Breaking change your API:

  • Major versioning sucks: avoid it.
  • Use automated contract testing:
    • Detect accidental changes in a contract.
      • Check current contract (/v3/api-docs) vs the one saved on git yesterday.
      • Use openapi-diff.
    • Failed CI on mismatch of consumed ↔ provided contract.
    • Auto-upgrade to newer versions of API client libraries.
      • Use renovate / dependabot submits PRs to upgrade version.

Expose internal Domain model:

  • Domain model freezes because clients depend on it, i.e. cannot make changes on your domain models.
  • Security risk as it can expose excessive data.
  • Domain pollution with presentation stuff like @JsonIgnore.

Force your clients to remote-call-in-loop:

  • API design can hurt performance.
    • E.g. only expose get-one-by-id GET /product/{id} clients will do for (id in list10k) { yourApi.get(id) }.
    • Expose a bulk retrieve:
      • GET /products?id=1,3,5 (⚠️ URL.length 2k chars)
      • POST /products/get-many + body
  • Don’t forget to add traces, especially for microservice architecture, to understand the response latency.

Reuse same DTO for POST/PUT/GET:

  • Can be misleading for clients, as they won’t know which fields to set, etc…
  • It will couple the endpoints → introduce change ripple.
  • The speaker suggests using Request / Response suffixes instead of DTO.
    • E.g. GetItemResponse get(id), void create(CreateItemRequest).
    • CQRS at the API level.
  • A tip for performance: use a repository that returns only the desired data directly in the controller, instead of the whole object.

Large PUT or PATCH, CRUD forever!

  • Large PUT can create concurrent updates, which can cause data loss by blind overwrite.
  • Instead, split the updates by which fields are updated on the user flow.
    • E.g. PUT /items/13/details, PUT /items/13/deactivate, PUT /item/13/cost, PUT /items/13/sell
      • Intentional API for clients.
      • Less concurrency risk (finer-grained).
      • Simpler on the server (no need to diff).
      • Requires user research.
      • More APIs.
      • More screens → full-stack.

Religious REST fallacy:

  • Do not follow REST design blindly as it can lead to:
    • unbalanced complexity;
    • limiting API semantics (CRUD).
  • Enhance it with:
    • sub-resources, e.g. GET or PUT /items/13/cost;
    • actions: POST /items/13/sell.
  • Try to stick to REST for as long as it’s decent.
  • See REST next level - crafting domain driven web APIs.
  • Avoid PATCH, not useful at all, and lacks semantics.