Photo by Elena Mozhvilo on Unsplash
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
andnumber
- 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