Course:Node.js & Express/
Lesson

You can get the routing, methods, and status codes perfectly right and still ship an APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. that's painful to work with, because the response bodies are inconsistent, error messages are unstructured, or paginationWhat is pagination?Splitting a large set of results into smaller pages so the server and client only handle a manageable chunk at a time. is missing. Response design is where API usability lives. It's the difference between an API that clients can rely on and one they have to defensively work around.

Consistent response structure

The most important rule is consistency. If your /users endpointWhat is endpoint?A specific URL path on a server that handles a particular type of request, like GET /api/users. returns { data: [...] } and your /orders endpoint returns [...] directly, every client has to special-case every endpoint. Pick a structure and use it everywhere.

Single resource

json
{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T10:30:00Z"
}

Single resources can be returned directly, no wrapper needed, because you're not going to add paginationWhat is pagination?Splitting a large set of results into smaller pages so the server and client only handle a manageable chunk at a time. metadata to a single item.

Collection response

Always wrap collections in an object, even if it feels like extra work up front:

json
{
  "data": [
    { "id": 123, "name": "John Doe", "email": "john@example.com" },
    { "id": 124, "name": "Jane Smith", "email": "jane@example.com" }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "limit": 20,
      "total_pages": 5,
      "total_count": 100
    }
  }
}

The reason for the wrapper: if you return a raw array and later need to add a meta field, you've broken every client that expected an array. The wrapper gives you a stable envelope you can extend without breaking changes.

02

PaginationWhat is pagination?Splitting a large set of results into smaller pages so the server and client only handle a manageable chunk at a time. strategies

Any endpointWhat is endpoint?A specific URL path on a server that handles a particular type of request, like GET /api/users. that can return more than a few records needs pagination. Don't let clients download your entire database.

Offset-based pagination

Simple, and clients can jump to any page:

json
{
  "data": [...],
  "meta": {
    "pagination": {
      "page": 2,
      "limit": 25,
      "total_pages": 10,
      "total_count": 250
    },
    "links": {
      "first": "/users?page=1&limit=25",
      "prev": "/users?page=1&limit=25",
      "next": "/users?page=3&limit=25",
      "last": "/users?page=10&limit=25"
    }
  }
}

The downside: SELECT * FROM users LIMIT 25 OFFSET 10000 is slow on large tables because the database scans and discards the first 10,000 rows.

Cursor-based paginationWhat is cursor-based pagination?A pagination method that uses an indexed column value from the last seen row rather than OFFSET, staying fast on very large tables.

Better for large datasets and real-time data:

json
{
  "data": [...],
  "meta": {
    "pagination": {
      "has_more": true,
      "next_cursor": "eyJpZCI6MTIzfQ=="
    }
  }
}

The cursor encodes the position in the dataset (usually the ID or timestamp of the last item). The next request sends ?cursor=eyJpZCI6MTIzfQ== to get the next page. Fast, consistent, but you can't jump to page 7 directly.

Use offset pagination for internal admin interfaces where jumping to a page matters. Use cursor pagination for feeds, activity streams, or any data that changes frequently while users are browsing.
03

Error response format

Plain text error messages break client code. JSONWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it. errors that vary in structure are almost as bad. Pick a shape and use it everywhere:

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request validation failed",
    "details": [
      {
        "field": "email",
        "message": "Email is required"
      },
      {
        "field": "password",
        "message": "Password must be at least 8 characters"
      }
    ],
    "request_id": "req-123456789"
  }
}

The code field is a machine-readable string your client can switch on. The message is human-readable. details gives field-level validation errors. request_id lets you correlate the error to server logs.

04

Naming conventions

Pick one convention and stick to it. The two common choices for JSONWhat is json?A text format for exchanging data between systems. It uses key-value pairs and arrays, and every programming language can read and write it. APIs:

json
// snake_case - common in Python, Ruby, many public APIs
{
  "first_name": "John",
  "created_at": "2024-01-15"
}

// camelCase - common in JavaScript-heavy stacks
{
  "firstName": "John",
  "createdAt": "2024-01-15"
}

Never mix them. { "firstName": "John", "created_at": "2024-01-15" } is the worst possible outcome.

05

Timestamps and null fields

Always use ISO 8601What is iso 8601?An international date and time format standard (e.g., 2024-03-15T10:30:00Z) used in APIs for unambiguous timestamps. format in UTC for timestamps. Never use Unix timestamps in responses, they're harder to read and debug:

json
{
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T14:45:30Z"
}

For null fields, be consistent: either always include them as null, or always omit them. The safer choice is to include them as null for fields that could have values, omitting them forces clients to check for both undefined and null.

06

Embedding relationships

When one resource references another, you have two choices:

json
// Embedded - include the related object inline
{
  "id": 123,
  "user": {
    "id": 456,
    "name": "John Doe"
  }
}

// Referenced - include the ID and let clients fetch if needed
{
  "id": 123,
  "user_id": 456
}

Embed when the related data is almost always needed with the parent. Reference when it's only sometimes needed, or when it would create circular dependencies. Some APIs let the client choose: GET /orders/123?include=user,items.

07

Quick reference

ScenarioStatus codeResponse body
GET resource200Resource object
GET collection200{ data: [...], meta: {...} }
POST (created)201Created resource + Location header
PUT / PATCH200Updated resource
DELETE204Empty
Not found404{ error: { code, message } }
Validation failed422{ error: { code, message, details } }
Server error500{ error: { code, message, request_id } }