Inconveniences with Conventional REST APIs

Keep it simple, stupid.

This article is about my personal inconveniences with conventional REST API approaches, and my preferred ways of dealing with it.

Path Parameters vs Query Parameters

Path parameters:

# path parameters (conventional)
/users/1/items/2
  • hard to parse, might need regular expressions
  • overly redundant, same as /items/2

Query parameters:

# query parameters (preferred)
/items?id=2
  • easier to parse, with browser's built-in URLSearchParams
  • short and precise

Now on deeply nested objects.

If we'll list an organization's projects, the following seems alright

# path parameters (conventional)
/organizations/2/projects

# query parameters (preferred)
/projects?organization_id=2

But if we'll list an organization's project's users, the difference becomes more clear

# path parameters (conventional)
/organizations/2/projects/4/users

# query parameters (preferred)
/users?project_id=4

Here we can notice the URL is less redundant and more direct.

Page URLs and HTTP Requests

Conventional

Action: List all items
Page URL: /items
HTTP Request: GET /items

Action: Create an item
Page URL: /items/new
HTTP Request: POST /items

Action: Read an item
Page URL: /items/id
HTTP Request: GET /items/id

Action: Update an item
Page URL: /items/id
HTTP Request: PUT /items/id
HTTP Request: PATCH /items/id

Action: Delete an item
Page URL: /items/id
HTTP Request: DELETE /items/id
  • cons: same page URL for viewing and updating an item
  • cons: item id uses path parameters

Preferred

Action: List all items
Page URL: /items
HTTP Request: GET /items

Action: Create an item
Page URL: /items/create
HTTP Request: POST /items

Action: Read an item
Page URL: /items/read?id=XYZ
HTTP Request: GET /items?id=XYZ

Action: Update an item
Page URL: /items/update?id=XYZ
HTTP Request: PUT /items?id=XYZ
HTTP Request: PATCH /items?id=XYZ

Action: Delete an item
Page URL: /items/delete?id=XYZ
HTTP Request: DELETE /items?id=XYZ
  • pros: different page URL for viewing and updating an item
  • pros: item id uses query parameters
  • pros: consistent with CRUD pattern

Object IDs

Auto-incrementing Numerical ID's

  • usually in the form of integer, bigint, serial, bigserial
  • cons: users can easily access the next object
  • cons: JavaScript requires casting between string and number
  • cons: JavaScript is limited to Number.MAX_SAFE_INTEGER

UUIDv4 (128-bit), or 256-bit randomly generated strings

  • pros: users cannot easily access the next object
  • pros: everything is a string, no need for type-casts
  • note: to sort by insertion order, option 1: use the new uuid formats ietf rfc, yc discussion
  • note: to sort by insertion order, option 2: use a separate timestamptz column for a more accurate representation

Authentication

Cookies

  • cons: use of cookies is simply bearer authentication, not hmac authentication
  • cons: cookies are usually not locked with the ip address and user agent
  • cons: cookies are a mess to work with cross-origin requests

Recommendations

  • use of correct response status
  • TODO

Authorization

Recommendations

  • use of correct response status
  • TODO

Metadata

Use of Headers

  • cons: all values are string-only, no numbers or booleans, unless you type-cast
  • cons: all values are flat, no nested

Structured Request & Response Bodies

interface request_body<T> {
  metadata: Record<string, string|number|boolean>;
  data: T;
}
interface response_error {
  code: string;
  message: string;
}
interface response_body<T> {
  metadata: Record<string, string|number|boolean>;
  data: T;
  error: response_error;
}
  • example request metadata: e.g. limit and offset for pagination
  • example response metadata: e.g. page and cursor for pagination, total item count, total query time in ms
  • TODO