REST

We exclusively use:

  • JSON or images as content.
  • GET to retrieve resources.
  • POST to submit new resources to the server.
  • PUT to fully update an existing resource (fails if it does not exist).
  • PATCH to update parts of an existing resource (fails if it does not exist).
  • DELETE to remove resources.

Both PUT and PATCH should be implemented idempotent. I.e., doing the same API calls again must be a NOP.

Status Codes

On success (e.g., when mutating the database), a 2XX status code should be returned. For validation errors, a 4XX status code is appropriate. A 5XX status code indicates a server error.

Returning 5XX is always a bug.

For more information on semantically correct status codes, please refer to MDN - HTTP response status codes.

Endpoints

The endpoint paths use:

  • hierarchical structure
  • nouns instead of verbs
  • only plural (exception: config)
  • all endpoints need authorization (exception: config)
  • all paths below /api
  • layer-specific paths below /maps/<map_id>/layers/<name of layer>/ e.g. /maps/<map_id>/layers/plants/plantings

Parameters

  • Use singular if there is only one item, plural for collections.
  • Search should have its own endpoint and parameter should be name
  • Return values are called Page* if they support paging. We use the parameter page and per_page (type integer) for pagination.
  • Currently we don't use filtering or sorting.
  • If in doubt, leave it out: keep parameters minimal.

Versioning

The frontend is the only user, so we only need minimal API versioning. The frontend only need to know if a reload is needed.

Files

We use utility functions to access files in Nextcloud.

Documentation

Documentation of APIs is done via utopia:

  • utoipa::path must be present for every endpoint e.g. #[get(...)]
  • all possible responses should be documented
    • specific 2xx codes, e.g., we use 201 Created for successful POST requests
    • all ways client could behave wrongly1 using 4xx error codes

1 i.e., how the API could be wrongly used (preconditions not met etc.)

Example:

#[utoipa::path(
    context_path = "/api/maps",
    responses(
        (status = 200, description = "Fetch a map by id", body = MapDto)
    ),
)]

Security

All endpoints except of /api/config must use Keycloak's jsonwebtoken and indicate so using:

#[utoipa::path(
    security(
        ("oauth2" = [])
    )
)]

Further Readings