Skip to content
Backend Development

REST API Design: Common Mistakes and Better Practices

Resource naming, status codes, errors, pagination and versioning — the design decisions that make an API pleasant to use and easy to evolve.

Aditya Sharma4 min read
API specification listing GET, POST, PATCH and DELETE endpoints next to a structured JSON error response
On this page

An API is a user interface for developers. Like any interface, small inconsistencies add up to confusion, bugs and support requests. The good news is that most REST API problems come from a short list of mistakes. Here they are, each with a better practice you can apply today.

Mistake 1: Verbs in URLs

POST /createArticle
GET  /getArticleById?id=42
POST /deleteArticle/42

HTTP already has verbs. URLs should name resources (nouns), and the method says what to do with them.

POST   /articles          create
GET    /articles/42       read one
GET    /articles          list
PATCH  /articles/42       partial update
DELETE /articles/42       delete

Use plural nouns consistently and nest only when the relationship is real and shallow, such as /articles/42/comments. Avoid deep chains like /users/7/articles/42/comments/3/likes.

Mistake 2: Everything returns 200

Returning 200 OK with {"success": false} forces every client to parse the body to discover an error, and it hides problems from monitoring tools. Status codes are part of your contract.

SituationStatus
Read succeeded200 OK
Resource created201 Created (+ Location header)
Accepted for async processing202 Accepted
Success, nothing to return204 No Content
Invalid input400 Bad Request or 422 Unprocessable Content
Not authenticated401 Unauthorized
Authenticated but not allowed403 Forbidden
Does not exist404 Not Found
Conflicts with current state409 Conflict
Rate limit reached429 Too Many Requests
Server bug500 Internal Server Error

Mistake 3: Inconsistent error bodies

If one endpoint returns {"error": "bad"} and another returns {"message": "...", "code": 17}, clients need special cases everywhere. Pick one shape. The Problem Details format (RFC 9457) is a well-supported standard:

{
  "type": "https://api.example.com/problems/validation",
  "title": "Validation failed",
  "status": 422,
  "detail": "One or more fields are invalid.",
  "errors": [
    { "field": "slug", "message": "must be unique" },
    { "field": "title", "message": "must not be blank" }
  ]
}

Make errors actionable: say which field failed and why. Never leak stack traces or SQL to clients.

Mistake 4: Unbounded lists

GET /articles that returns every row works in development and falls over in production. Paginate every collection from day one.

  • Offset pagination (?page=3&per_page=20) is simple and fine for small, stable datasets.
  • Cursor pagination (?after=eyJpZCI6MTIzfQ&limit=20) stays fast on large tables and does not skip or duplicate items when data changes between requests.
{
  "data": [ { "id": 124, "title": "..." } ],
  "page": { "next_cursor": "eyJpZCI6MTQ0fQ", "has_more": true }
}

Set a sensible default page size and a maximum, and document both.

Mistake 5: Ignoring idempotency

Networks fail. Clients retry. If a retry of POST /payments creates a second payment, you have a serious bug. GET, PUT and DELETE are idempotent by definition; POST is not.

For operations that must not happen twice, accept an idempotency key header. The server stores the key with the result and returns the same response for repeated requests with the same key.

POST /payments
Idempotency-Key: 5f1c2d7e-9b1a-4c0e-8d53-2a1b9e6f7c10
Content-Type: application/json

Mistake 6: Breaking clients without warning

Renaming a field or changing its type breaks every client that depends on it. Strategies that help:

  1. Additive changes are safe: adding optional fields or new endpoints rarely breaks anyone.
  2. Version when you must break: a URL prefix (/v2/articles) is the most visible and easiest to route; a header-based version is cleaner but harder to test in a browser.
  3. Deprecate before removing: announce, add a Deprecation header or documentation note, monitor usage, then remove.

Mistake 7: Over- and under-fetching

Returning huge nested objects wastes bandwidth; returning too little forces clients into many round trips. Useful middle grounds:

  • Sparse fieldsets: GET /articles?fields=id,title,slug
  • Explicit expansion: GET /articles/42?include=author
  • Purpose-built endpoints for heavy screens when needed.

Mistake 8: Weak security defaults

  • Serve everything over HTTPS only.
  • Authenticate with standard mechanisms (OAuth 2.0 / OpenID Connect, or signed tokens) — do not invent your own.
  • Authorise every request at the resource level: can this user access this article?
  • Validate and limit input sizes; reject unknown fields where it makes sense.
  • Rate-limit and return 429 with a Retry-After header.

Document it like a product

Describe the API with OpenAPI. A spec file lets you generate reference documentation, client SDKs and contract tests, and it forces the team to agree on names and shapes before code is written.

paths:
  /articles/{id}:
    get:
      summary: Get an article
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200": { description: The article }
        "404": { description: Not found }

A good API is boring in the best way: predictable URLs, honest status codes, consistent errors and changes that never surprise the people who depend on it.

Written by

Aditya Sharma

Backend developer · Java & APIs

Enjoys taking apart backend frameworks to see how requests really flow, and writing down what he finds. (Demo author profile.)

Comments

Join the discussion

Your email address will not be published. Be kind and stay on topic.