Second approach: The right way
If you missed the first approach, it can be found here. In this one i would like to talk separately about different means to create a unique way to resources and methods of your Web-API and about architectural specificities that influence on the look of this way and its components.
What to think about before you set sail
Sooner or later each existing system starts to evolve: develop, become more complex, large, modern. For REST API developers it means the necessity to launch new API versions instead of old ones. Here I’m talking not about architectural changes inside your system, but about changes in the very data format and a set of operations applicable to it. In any case it is necessary to provide for version control both in organization of source code and in principle of URL building. As for URL, there are two popular ways to define the version of API, which the request is addressed to. These are prefixing of a path example-api.com/v1/ and and distinction of verions on subdomain level v1.example-api.com. Both can be used depending on needs and necessities.
Autonomy of components
Web-API of complex systems supporting several user roles often requires splitting into parts, each of which attends to a specter of tasks. In fact, each part may be a separate application and work on different machines and platforms. In terms of API we don’t care how a server processes a request and which forces and technologies are involved. For a client API is an encapsulated system. Nevertheless, different parts of a system may have absolutely different functionality, for example, admin and user parts. So the methodology of work with, it seemed, the same resources may differ significantly. That’s why these parts should be separated on the domain level; admin.v1.example-api.com or with the help of a path prefix example-api.com/v1/admin/. This requirement is not mandatory, and a lot of things depend on the system complexity and its purposes.
Data exchange format
As for me, the most common and functional data exchange format is JSON, but nobody prohibits to use XML, YAML or any other format, allowing to store serialized objects without losing data type. If desired, it is possible to make API support several input/output formats. For this, one needs to use an HTTP header of request to define the desired format of reply, and Accept and Content-Type to indicate format of data, transmitted in the request. Another popular way is to add an extension to the resource URL, for example, GET /users.xml, but this way seems less flexible and beautiful, at least because it weighs URL and is correct for GET-requests rather than all the possible operations.
Localization and multi-language support
In practice multi-language API support is mainly used to translate service messages and error messages to the required language to display to the final user. Multi-language content also exists, but as for me, saving and providing content should be differed in a more clear way. For example, if you have the same article in two languages, in fact these are two entities, grouped by the unity of the content. Several ways can be used to define a required language.The most common is a standard HTTP header Accept-Language. I’ve met other ways, like adding a GET-parameter language=”en”, using path prefix example-api.com/en/ or even on the domain name level en.example-api.com. I think, the way of indicating localization depends on the app and the tasks, that it must fulfill.
So, we have come to the root node of our API (or one of its components). All the further paths will be performed inside your server app, according to the set of supported resources.
Routes to collections
In order to define a path to a collection, we just use the name of the corresponding entity, for example, if it is a users list, the path will be /users. Two methods may be applied to a collection as it is: these are GET (getting a limited list of entities) and POST (creating a new element). In requests for list getting we can use a lot of additional GET parameters, used for pagination, sorting, filtering, search etc., but they must be optional, i.e. these parameters must not be transmitted as a part of the path!
Elements of collection
In order to address a single element of a collection we use its unique ID /users/25 in the path . This is the unique path to an element. For working with an object the methods GET (getting an object), PUT/PATCH (changing an object) and DELETE (deleting) may be applied.
In a variety of services there are objects, that are unique for the current user, like the profile of a current user /profile or personal settings /settings. Surely, on the one hand these are elements of a collection, but they are the point of departure in use of our Web-API by a client application, furthermore, they allow to perform a significantly wider set of operations with data. That said, a collection storing user settings may be not available for security reasons and confidential data protection.
Properties of objects and collections
In order to get access to any of properties directly, one needs to add the name of a property to a path, like getting users name /users/25/name.The methods GET (getting a value) and PUT/PATCH (changing a value) may be applied to a property. The method DELETE cannot be applied, as a property is a part of the structure of an object as a formalized data unit.
In the previous part we have said that collections can have their properties as well as objects. As far as I remember I used only the property count, but your application may be more customer and complex. The paths to the properties of collections are built on the same principle, as the paths to the properties of their elements: /users/count. Only the method GET (getting a property) can be applied here, as a collection is only the interface to reach the list.
Collections of connected objects
One of varieties of objects properties may be related objects or collections of related objects. These entities are usually not properties of an object itself, but links to its connections with other entities. For example, a list of roles that have been assigned to a user: /users/25/roles. We will consider work with nested objects in one of the next parts, now it is sufficient to know that we can address them directly, like any other property of an object.
Functions of objects and collections
To create a path to the interface of a function call in a collection or an object, we use the same approach that we used for addressing a property. For example, for an object /users/25/sendPasswordReminder or a collection /users/disableUnconfirmed. For a function call we anyway use POST method. Why? Let me remind you that in classical REST we don’t have a special verb for a function call, therefore we have to use one of the existing. In my opinion, POST suits the best as it allows to transferall the necessary arguments to the server, it isn’t idempotential (doesn’t return the same result in case of repeated calls) and is the most abstract by its semantics.
I hope, it has been digested. :) In the next part we will talk about calls and replies, their formats and codes of statuses.