API Guidelines

This issue serves to collect our common understanding how to design our API so that it is extensible and usable and understandable.

Resource oriented

A resource-oriented API is generally modeled as a resource hierarchy, where each node is either a simple resource or a collection resource. For convenience, they are often called a resource and a collection, respectively.

Examples of Resource Nouns:

machine user flake

Often resources have sub-resources. Even if it is not foreseen, it is recommended to use plural (trailing s) on resources to allow them to be collections of sub-resources.

e.g,

users -> users/*/profile

Verbs

Verbs should not be part of the URL

Bad: /api/create-products

Good: /api/products

Only resources are part of the URL, verbs are described via the HTTP Method.

Exception:

If a different HTTP Method must be used for technical reasons it is okay to terminate the path with a (short) verb / action.

Okay ish: /api/products/create

Usually the following HTTP Methods exist to interact with a resource

  • POST (create an order for a resource)
  • GET (retrieve the information)
  • PUT (update and replace information)
  • PATCH (update and modify information) (Not used yet)
  • DELETE (delete the item)

Every resource should be CRUD compatible

All API resources MUST be designed in a way that allows the typical CRUD operations.

Where crud stands for:

C - Create R - Read U - Update D - Delete

Resources should implement at least a "Read" operation.

Body

Use JSON as an exchange format.

All responses MUST be JSON parseable.

Bad: bare string

Better: "quoted string"

Best: (Enveloped see next section) { name: "quoted string"}

Errors should have a consistent JSON format, such that it is clear in which field to look at for displaying error messages.

Envelop all Data collections

Response data should be wrapped into an JSON Object {} Lists [] should also contain Objects {}. This allows everything, to be extensible, without breaking backwards compatibility. (Adding fields is trivial, since the schema doesn't change)

Example:

{
   "users": [{
     first_name: "John",
     last_name: "Doe",
   }, {
     first_name: "Jane",
     last_name: "Doe",
   }
  ....
  ],
   "skip": 0,
   "limit": 20,
  ....
}

Bad Example of a breaking change: GET /api/flakes old

[
  "dream2nix"
  "disko"
]

new

[
 {
    name: "dream2nix",
    url: "github/...."
 },
 {
    name: "disko",
    url: "github/...."
 }
]

Those kind of breaking changes can be avoided by using an object from the beginning. Even if the object only contains one key, it is extensible, without breaking.

More will follow.

...maybe