Naming things is difficult...

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": ... }
  • PUT /api/v1/customers/1
    • response: 200 { "id": 1, "name": ... }

 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": ... }
  • PATCH /api/v1/customers/1
    • response: 200 { "id": 1, "name": ... }
  • DELETE /api/v1/customers/1
    • response: 200

 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...)

22