API Design
During the design of Alexandria’s API, we aimed at following REST principles [3] as outlined in Roy Fielding’s 2000 dissertation. Within the constraints of our application in its current state of development, we arrived at the following.
Client-Server This separation came naturally with the decision to create a React-based single page application.
Stateless For security reasons and device interchangeability, only the user id is stored with the client in a JSON Web token, sent with every request. Additional information is drawn from a Recoil atom that keeps track of the user state. The request includes the combined state information, and sends it to the server.
Cacheable The core of Alexandria - the single text and translation page, is a highly dynamic affair, where cached responses for word and translation queries could seriously damage the user experience. We explicitly allowed the caching of responses only for resources that do not change often: list of languages, web dictionary list.
Layered System Front and back end of Alexandria reside on different servers, making scalability independent of each other.
Code-on-Demand This was not actively implemented in Alexandria.
Uniform Interface According to Fielding, “In order to obtain a uniform interface, multiple architectural constraints are needed to guide the behavior of components: …
- identification of resources,
- manipulation of resources through representations,
- self-descriptive messages,
- hypermedia as the engine of application state.”
During API development, significant effort was expended in getting the uniform interface right. We focused on refining a system of URLs (routes) that allows the clients or future contributors to retrieve data that is logical, predictable, and readable.
Here is an overview of our API endpoints.
Resources and collections
- Resources are the individual items (rows) of the main database tables (the once with a “single noun” name). Each resource has a unique id within its table.
- Collections are lists of resources, eg. words or texts, that can be filtered by criteria such as languages or user.
Route patterns
Basic access
- Type of the resource queried is always first part of the route.
- This is the case for both individual resources and collections.
- Queried resource is named in plural form.
- Individual resources are accessed by their id.
- Data from other resources associated with the queried resource might be sent back as well, but querying those happens on the back end and is not exposed via the API.
- Creation, updates and deletion of resources happens via the basic route, ie. without filters (see below).
Examples
GET /texts/
=> a collection of all textsGET /texts/38422
=> the text with id38422
POST /translations
=> create a new translationPUT /users
=> update user dataDELETE /texts/38422
=> removes text with id22533
Filtering collections
- If the second part of a route is not an id, we are looking for a filtered collection of that resource.
- Filters are indicated by a keyword (in singular) and an unique identifier.
- Filters can be chained.
- Filtering a logged-in user is indicated via the URL. Filtering collections belonging to a user is done on the back end and based on the functionality and access rights of the app.
Examples
GET /words/text/17/language/fr
=> get all words from text 17 that have a translation to French (unique identifierfr
)GET /texts/language/fr
=> all texts in French (unique identifierde
) belonging to the current user
Special routes
Certain functionality that the client demands from the server did not fit the resource schema outlined above, e.g. login, logout, or user related functions. In those cases, route URLs deviate from the pattern after the initial resource, and spell out what is expected of them.