I was recently asked to write a section for our company's new developer handbook on API design...
Top to bottom.... here we go...
You might have seen in the wild, a request like this:
/api/v2/customers/24/orders/1
or
/products/56/variants
Both of these examples are pretty sound design choices, and here's why:
Prefixing
It's a good idea to start the API routes with a prefix signalling that this path is indeed an API. It instantly warns developers that the end points are unlikely to return a human readable response out of the box...
So... /api
all the way.
Unless you've got a subdomain, like https://api.mydomain.com
where this becomes more redundant...
Β Versioning
If you're designing a greenfield API, that is, no existing legacy API to write over, or replace, then it's a good idea to start with the idea that your brand new API might be replaced some day
In order to do that, one approach is to 'version' or 'namespace' the API. In this way, clients consuming the API (front-ends, data-only clients) can easily configure themselves to work against any specified / working version of the API and know what to get...
- Need to deprecate a method? New version
- Need to make a breaking change to shape of data response? New version
- Need to modify the parameters that an API accepts? New Version
So
/api/v1
or /api/v2/
and so on...
RESTful resources
REST APIs (aka representational state transfer) APIs are of course not the ONLY way of defining an API, we've got SOAP/XML, RPC, and many many other protocols...
REST is also more of a concept and protocol that an API should conform to, than a hard and fast set of rules, but they make a lot of sense and are very easy to reason about
HTTP methods
The first thing to consider is to design our API in terms of the 5 most common HTTP methods: GET, POST, PUT, PATCH, DELETE
- GET for reading/viewing/listing
- POST for creating (*)
- PUT for creating /replacing (*)
- PATCH for updating
- DELETE well...
(* explained later)
The important thing to note is a GET
request should invariably never have side effects -- that is it shouldn't change the state of a resource... talking of resources...
Designing for resources / entities
The next big thing is that we're usually going to be designing the API against a bunch of entities that we've designed in our back-end... for example Users
or Customers
or Orders
etc.
Some of these resources might be embedded in each other, and some might be stand-alone, but via convention we should be able to address this...
Β URL format
A note about entity naming in URLS: Every programming language is different, they use CapitalCase
, camelCase
, kebab-case
, snake_case
, etc. A URL is not a programming language, stick to kebab-case
- URLS are case insensitive and you enhance readability by exposing the idiosyncrasies of your back-end language to your user!
Β Fetching multiple resources:
The entity should be pluralised and be the first item in the path
GET /api/v1/customers
GET /api/v1/users
GET /api/v1/orders
Sometimes these resources required pagination and in almost all cases, the GET
request should expect this to be done via query strings
GET /api/v1/customers?page=1&page_size=20
Β Fetching single resources:
The same path as before, with a unique ID appended in the path:
GET /api/v1/customers/12
GET /api/v1/users/1
GET /api/v1/orders/24
It's almost always preferred to use integer IDs over string IDs in a SQL-based or RDBM system. (Don't get tied u over this, though, it's beyond the scope of this article)
You might want to allow (via parameters) restricting the information that comes back about a single entity... again, query strings (not a request body)
GET /api/v1/customers/1?fields=id,firstname,lastname
Β Creating a resource:
POST/PUT are the two choices here. If we're being strict with REST definitions, then POST is for creating without regard to idempotency (idempotency is the idea that a repeat operation like create should only happen once even with repeated calls) and PUT should be used for replacing or updating a resource, or you're creating with an expected ID or unique identifier.
Obvioulsy parameters go in the BODY as application/json
or application/url-form-encoded
or whatever you require
Either way, they should be name with the pluralised endpoint:
-
POST /api/v1/customers
- response:
201 { "id": 12, "name": ... }
- response:
-
PUT /api/v1/customers/1
- response:
200 { "id": 1, "name": ... }
- response:
Β Updating a resource:
Updating can be done with PUT
(see above) or PATCH
. PATCH is usually reserved for partial updates of a resource. DELETE
is well, for removing a resource
Naming: same as above - use the pluralised endpoint + a unique identifier.
-
PUT /api/v1/customers/1
- response:
200 { "id": 12, "name": ... }
- response:
-
PATCH /api/v1/customers/1
- response:
200 { "id": 1, "name": ... }
- response:
-
DELETE /api/v1/customers/1
- response:
200
- response:
Β Nested resources:
Ok, gets a bit tricky, but in general the same rules are just re-used going on down the tree!
A Customer
might have Orders
or Bussiness Units
or even a single one-to-one attribute like Address
GET /api/v1/customers/1/orders
GET /api/v1/customers/1/orders/1
POST /api/v1/customers/1/orders
GET /api/v1/customers/1/business-units
GET /api/v1/customers/1/address
Β Authentication
Don't put your API keys in query strings, folks! It's just bad practice. An authentication request is full of side effects. POST
it! And put it in the request body, these things can show up in browser histories and server logs and are a big no no!
-
POST /api/v1/auth/login
{
"username": "...",
"password": "..."
}
Final thoughts
All in all, there's generally very little reason to name a resource in a URL in the singular unless it's an embedded entity as mentioned above (Address
), or when the resource cant really be pluralised (hello Advice
or Evidence
)
Otherwise, it's a spelling mistake, and you should be ashamed of yourself. (Only joking...)
Top comments (0)