Skip to main content
Version: Next

REST API

The core of our system consists of several services that provide their functionality through REST API. These are only the API's that are used internally by DIVA and should not be available to third party providers. Since the services do not implement authentication logic, the service must always run behind the Gateway.

Best practises

The number of services will potentially continue to grow. This makes it more complicated to implement a system-wide consistent, clean and simple API. This guide provides us with a reference for developing the APIs in a consistent manner and helps us make the right decisions.

The rules listed below are a derivation of the general REST API best practices. We only mention the points that are relevant to us here. This guide is not intended to teach you REST API. Furthermore, REST is a style guide, so we adopt some practices to the architecture of our system.

Legend

✅ - do

🛑 - don't

⚡ - not recommended

Describe your API with OpenAPI

All services document their interfaces with OpenAPI. Please write the definition as detailed as possible and explain all existing edge cases. All parameters, return values, etc. must have clear and strict type definitions.

Use nouns to represent Resources

In REST, primary data representation is called Resource. Do not use verbs to express manipulations on resources, as they are presented by HTTP methods.

✅ service/users
🛑 service/getUsers

Name collections with plural nouns

In most cases our REST interfaces represent collections. By using the plural it becomes obvious that we are working with a set of resources (e.g. users). To express manipulation on a single resource use an endpoint with id path parameter.

✅ users
✅ users/:id
🛑 user
🛑 user/:id

Controller archetype

Sometimes the HTTP verbs are not sufficient to express an action on resources. Use Controller Resources to express special actions.

✅ POST users/:id/verify

Pagination, projection and filtering

The APIs must provide pagination, projection and filtering for resources.

Pagination

Pagination is the most important concept and must be present in all APIs. We also recommend to fallback to a default value for the page size, if not defined in the request. But make sure that this value is clearly documented. The response must contain a cursor that can be used to query the next page. The cursor value is dependent on the technology used in the service, but the client does not have to worry about the internal mechanisms. If there is no cursor in the response, there is no further page. Also make sure to include the total number of entities as total that could be returned according to current filtering conditions.

✅ GET resources RESPONSE BODY {"collection": [50], total: 1300, cursor: "12g3hhgfhg1f32"}
✅ GET resources?cursor=12g3hhgfhg1f32
✅ GET resources?cursor=12g3hhgfhg1f32&pageSize=20

Projection

In oder for the client to be able to select the fields of a resource, the API must support projection. The fields query parameter that is a String with coma separated fields in it must be supported.

✅ GET users?fields=name,email,imageId

Filtering

To keep the API simple and consistent, the API muss support simple filtering possibilities by specific entity fields with a general scheme [field]=[value]. In the corresponding OpenAPI specification it must be documented by which fields the entities can be filtered.

✅ GET reviews?belongsTo=resource:uuid:some-id
🛑 GET users/admin
🛑 GET resources(type=pdf&rating>=4)

For resources, assets, user and other entity types in the future we have to consider more complex search functionality. The Search Assistant service is responsible for advanced search functionalities.

Summary

Response:

{
"total": 1300, // total number of entites
"collection": [], // returned array of entites
"cursor": "dajkshjkh234kh" // cursor to the next page
}

Query:

  • cursor - cursor for the next page
  • fields - coma separated list of projection fields as String
  • [someField] - entity specific filtering fields

Nesting Resources

There are different views regarding the representation of the hierarchy between resources. To be short, we prefer a flat structure without sub resources to keep the API as simple and clear as possible. In general, entities that can be seen as independent resources should have their own endpoint. Instead of the nesting we express the relations between Resources using our neo4j database. So entities that are in a relation to another entity can be connected by adding an edge between them. If we want to resolve relations we need to ask for the edges on the entity's node.

Flat Resources Structure

Try to keep the structure of all resources as flat as possible. This will help us to keep the individual documents in the collections clear and not too large. Especially try to avoid arrays of objects, which can potentially be very long. One example is the history. Logically you can see the history as a subresource of resources. But we expect that the history will grow over time and make the resource document very large. In this case the option to represent history as a separate REST resource should be considered.

Bulk request mode

The bulk mode allows to create and/or update multiple resources in one request. We should consider this option for our resources and asset APIs. The endpoints for creating new entities should support bulk mode, where an array of new resources is accepted. This can be useful when importing data sources that contain many individual resources.

Consistent Responses

Depending on the API endpoint and the status of the operation, we specify different response schemes. In all cases, the use of HTTP status code must be implemented consistently and semantically correct and documented in the OpenAPI specification.

  • Collection response (according to pagination):

      {
    "total": 1500,
    "collection": [],
    "cursor": "akjzdaztsgdz32jg="
    }
  • Error response:

      {
    "type": "string", // error type like "AuthentificationError"
    "message": "string", // detailed human readable message
    "code": 409, // error code, HTTP Status Code or some specific code
    "errors": [ // additional errors, mostly schema or specification validation errors
    {}
    ]
    }
  • Bulk collection response: Status 207

      [
    {
    "statusCode": 500, // status code for each operation in the bulk
    "data": "string", // additional data
    "error": { // error must be included in error case
    "type": "string",
    "message": "string",
    "code": 409,
    "errors": [
    {}
    ]
    }
    }
    ]