Mejores prácticas REST
REST significa Transferencia de Estado Representativo (REpresentational State Transfer) y es un estilo arquitectónico para sistemas hipermedia distribuidos (HTTP). Para ser considerado RESTful, una interfaz de servicio debe cumplir con sus principios y restricciones.
La definición de REST está basada en la disertación de Roy Fielding.
Una API web (o servicio web) que cumple con el estilo arquitectónico REST es una API REST.
Resumen (TL;DR)
En términos simples, en el estilo arquitectónico REST, los datos y la funcionalidad se consideran recursos y se accede a ellos usando Identificadores de Recursos Uniformes (URIs).
Los recursos son actuados mediante el uso de un conjunto de operaciones simples y bien definidas. Además, los recursos deben estar desacoplados de su representación para que los clientes puedan acceder al contenido en varios formatos, como HTML, XML, texto plano, PDF, JPEG, JSON y otros.
Los clientes y los servidores intercambian representaciones de recursos usando una interfaz y protocolo estandarizados. Típicamente HTTP es el protocolo más usado, pero REST no lo mandate.
Los metadatos sobre el recurso están disponibles y se utilizan para controlar la caché, detectar errores de transmisión, negociar el formato de representación adecuado y realizar autenticación o control de acceso.
Y lo más importante, cada interacción con el servidor debe ser sin estado.
Todos estos principios ayudan a las aplicaciones RESTful a ser simples, livianas y rápidas.
1. Los principios guía de REST:
The six guiding principles or constraints of the RESTful architecture are:
1.1. Interfaz Uniforme
Aplicando el principio de generalidad a la interfaz de los componentes, se puede simplificar la arquitectura general del sistema y mejorar la visibilidad de las interacciones.
Varias restricciones arquitectónicas ayudan a obtener una interfaz uniforme y guían el comportamiento de los componentes.
Las siguientes cuatro restricciones pueden lograr una interfaz REST uniforme:
- Identificación de recursos - La interfaz debe identificar de manera única cada recurso involucrado en la interacción entre el cliente y el servidor.
- Manipulación de recursos a través de representaciones - Los recursos deben tener representaciones uniformes en la respuesta del servidor. Los consumidores de la API deben usar estas representaciones para modificar el estado de los recursos en el servidor.
- Mensajes autodescriptivos - Cada representación de recurso debe llevar suficiente información para describir cómo procesar el mensaje. También debe proporcionar información sobre las acciones adicionales que el cliente puede realizar en el recurso.
- Hipermedia como motor del estado de la aplicación - El cliente solo debe tener la URI inicial de la aplicación. La aplicación cliente debe manejar dinámicamente todos los otros recursos y interacciones con el uso de hiperenlaces.
1.2. Cliente-Servidor
El patrón de diseño cliente-servidor aplica la separación de responsabilidades, lo que ayuda a que los componentes cliente y servidor evolucionen de manera independiente.
Separando las preocupaciones de la interfaz de usuario (cliente) de las preocupaciones de almacenamiento de datos (servidor), mejoramos la portabilidad de la interfaz de usuario en múltiples plataformas y mejoramos la escalabilidad simplificando los componentes del servidor.
A medida que el cliente y el servidor evolucionan, es importante asegurarse de que la interfaz/contrato entre el cliente y el servidor no se rompa.
1.3. Sin Estado (Stateless)
La característica de sin estado dicta que cada solicitud del cliente al servidor debe contener toda la información necesaria para entender y completar la solicitud.
El servidor no puede aprovechar ninguna información de contexto previamente almacenada en el servidor.
Por esta razón, la aplicación cliente debe mantener por completo el estado de la sesión.
1.4. Almacenable en caché
La restricción almacenable en caché requiere que una respuesta deba etiquetarse implícita o explícitamente como almacenable en caché o no almacenable en caché.
Si la respuesta es almacenable en caché, la aplicación cliente tiene derecho a reutilizar los datos de respuesta más tarde para solicitudes equivalentes y durante un período especificado.
1.5. Sistema en Capas
El estilo de sistema en capas permite que una arquitectura se componga de capas jerarquizadas restringiendo el comportamiento de los componentes.
Por ejemplo, en un sistema en capas, cada componente no puede ver más allá de la capa inmediata con la que está interactuando.
1.6. Código bajo Demanda (Opcional)
El estilo arquitectónico REST también permite que la funcionalidad del cliente se extienda descargando y ejecutando código en forma de applets o scripts. El código descargado simplifica a los clientes al reducir el número de características que deben estar preimplementadas. Los servidores pueden proporcionar parte de las funcionalidades entregadas al cliente en forma de código, y el cliente solo necesita ejecutar el código. Este es un aspecto opcional en REST.
2. ¿Qué es un recurso?
La abstracción clave de información en REST es un recurso. Cualquier información que podamos nombrar puede ser un recurso. Por ejemplo, un recurso REST puede ser un documento o imagen, un servicio temporal, una colección de otros recursos o un objeto no virtual (por ejemplo, una persona).
El estado del recurso en un momento dado se conoce como representación del recurso.
Las representaciones del recurso consisten en:
- los datos
- los metadatos que describen los datos
- y los enlaces de hipermedia que pueden ayudar a los clientes a transicionar al siguiente estado deseado.
Una API REST consiste en un ensamblaje de recursos interconectados. Este conjunto de recursos se conoce como el modelo de recursos de la API REST.
2.1. Identificadores de recursos
REST utiliza identificadores de recursos para identificar cada recurso involucrado en las interacciones entre los componentes cliente y servidor.
2.2. Hypermedia
El formato de datos de una representación se conoce como tipo de medio. El tipo de medio identifica una especificación que define cómo se debe procesar una representación.
Una API REST se parece a hipertexto. Cada unidad informativa con direccionabilidad lleva una dirección, ya sea explícitamente (por ejemplo, los atributos de enlace e id) o implícitamente (por ejemplo, derivado de la definición de tipo de medio y estructura de representación).
Hipertexto (o hipermedia) significa la presentación simultánea de información y controles de tal manera que la información se convierte en la posibilidad a través de la cual el usuario (o automático) obtiene opciones y selecciona acciones.
Recuerde que el hipertexto no necesita ser HTML (o XML o JSON) en un navegador. Las máquinas pueden seguir enlaces cuando comprenden el formato de datos y los tipos de relación.
- Roy Fielding
2.3. Auto-Descriptiva
Además, las representaciones de recursos deben ser auto-descriptivas: el cliente no necesita saber si un recurso es un empleado o un dispositivo. Debe actuar en base al tipo de medio asociado con el recurso.
Por lo tanto, en la práctica, crearemos muchos tipos de medios personalizados, generalmente un tipo de medio asociado con un recurso.
Cada tipo de medio define un modelo de procesamiento predeterminado. Por ejemplo, HTML define un proceso de renderizado para hipertexto y el comportamiento del navegador en torno a cada elemento.
Los tipos de medios no tienen relación con los métodos de recursos GET/PUT/POST/DELETE/… más allá del hecho de que algunos elementos de tipo de medios definirán un modelo de proceso que vaya como “elementos de anclaje con un atributo
hrefcrean un enlace de hipertexto que, al seleccionarse, invoca una solicitud de recuperación (GET) en la URI correspondiente al atributo codificado en CDATAhref.
3. Resource Methods
Another important thing associated with REST is resource methods. These resource methods are used to perform the desired transition between two states of any resource.
A large number of people wrongly relate resource methods to HTTP methods (i.e., GET/PUT/POST/DELETE). Roy Fielding has never mentioned any recommendation around which method to be used in which condition. All he emphasizes is that it should be a uniform interface.
For example, if we decide that the application APIs will use HTTP POST for updating a resource – rather than most people recommend HTTP PUT – it’s all right. Still, the application interface will be RESTful.
Ideally, everything needed to transition the resource state shall be part of the resource representation – including all the supported methods and what form they will leave the representation.
We should enter a REST API with no prior knowledge beyond the initial URI (a bookmark) and a set of standardized media types appropriate for the intended audience (i.e., expected to be understood by any client that might use the API).
From that point on, all application state transitions must be driven by the client selection of server-provided choices present in the received representations or implied by the user’s manipulation of those representations.
The transitions may be determined (or limited by) the client’s knowledge of media types and resource communication mechanisms, both of which may be improved on the fly (e.g., code-on-demand). [Failure here implies that out-of-band information is driving interaction instead of hypertext.]
4. REST and HTTP are Not the Same
Many people prefer to compare HTTP with REST. REST and HTTP are not the same.
REST != HTTP
Though REST also intends to make the web (internet) more streamlined and standard, Roy Fielding advocates using REST principles more strictly. And that’s from where people try to start comparing REST with the web.
Roy Fielding, in his dissertation, has nowhere mentioned any implementation direction – including any protocol preference or even HTTP. Till the time, we are honoring the six guiding principles of REST, which we can call our interface – RESTful.
Resource naming
1. What is a Resource?
In REST, the primary data representation is called resource. Having a consistent and robust REST resource naming strategy – will prove one of the best design decisions in the long term.
The key abstraction of information in REST is a resource. Any information that can be named can be a resource: a document or image, a temporal service (e.g. “today’s weather in Los Angeles”), a collection of other resources, a non-virtual object (e.g., a person), and so on.
In other words, any concept that might be the target of an author’s hypertext reference must fit within the definition of a resource.
A resource is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time.
1.1. Singleton and Collection Resources
A resource can be a singleton or a collection.
For example, “customers” is a collection resource and “customer” is a singleton resource (in a banking domain).
We can identify “customers” collection resource using the URI “/customers“. We can identify a single “customer” resource using the URI “/customers/{customerId}“.
1.2. Collection and Sub-collection Resources
A resource may contain sub-collection resources also.
For example, sub-collection resource “accounts” of a particular “customer” can be identified using the URN “/customers/{customerId}/accounts” (in a banking domain).
Similarly, a singleton resource “account” inside the sub-collection resource “accounts” can be identified as follows: “/customers/{customerId}/accounts/{accountId}“.
1.3. URI
REST APIs use Uniform Resource Identifiers (URIs) to address resources. REST API designers should create URIs that convey a REST API’s resource model to the potential clients of the API. When resources are named well, an API is intuitive and easy to use. If done poorly, that same API can be challenging to use and understand.
The constraint of a uniform interface is partially addressed by the combination of URIs and HTTP verbs and using them in line with the standards and conventions.
Below are a few tips to get you going when creating the resource URIs for your new API.
2. Best Practices
2.1. Use nouns to represent resources
RESTful URI should refer to a resource that is a thing (noun) instead of referring to an action (verb) because nouns have properties that verbs do not have – similarly, resources have attributes. Some examples of a resource are:
- Users of the system
- User Accounts
- Network Devices etc.
and their resource URIs can be designed as below:
http://api.example.com/device-management/managed-devices
http://api.example.com/device-management/managed-devices/{device-id}
http://api.example.com/user-management/users
http://api.example.com/user-management/users/{id}
For more clarity, let’s divide the resource archetypes into four categories (document, collection, store, and controller). Then it would be best if you always targeted to put a resource into one archetype and then use its naming convention consistently.
For uniformity’s sake, resist the temptation to design resources that are hybrids of more than one archetype.
2.1.1. document
A document resource is a singular concept that is akin to an object instance or database record.
In REST, you can view it as a single resource inside resource collection. A document’s state representation typically includes both fields with values and links to other related resources.
Use “singular” name to denote document resource archetype.
http://api.example.com/device-management/managed-devices/{device-id}
http://api.example.com/user-management/users/{id}
http://api.example.com/user-management/users/admin
2.1.2. collection
A collection resource is a server-managed directory of resources.
Clients may propose new resources to be added to a collection. However, it is up to the collection resource to choose to create a new resource or not.
A collection resource chooses what it wants to contain and also decides the URIs of each contained resource.
Use the “plural” name to denote the collection resource archetype.
http://api.example.com/device-management/managed-devices
http://api.example.com/user-management/users
http://api.example.com/user-management/users/{id}/accounts
2.1.3. store
A store is a client-managed resource repository. A store resource lets an API client put resources in, get them back out, and decide when to delete them.
A store never generates new URIs. Instead, each stored resource has a URI. The URI was chosen by a client when the resource initially put it into the store.
Use “plural” name to denote store resource archetype.
http://api.example.com/song-management/users/{id}/playlists
2.1.4. controller
A controller resource models a procedural concept. Controller resources are like executable functions, with parameters and return values, inputs, and outputs.
Use “verb” to denote controller archetype.
http://api.example.com/cart-management/users/{id}/cart/checkout http://api.example.com/song-management/users/{id}/playlist/play
2.2. Consistency is the key
Use consistent resource naming conventions and URI formatting for minimum ambiguity and maximum readability and maintainability. You may implement the below design hints to achieve consistency:
2.2.1. Use forward slash (/) to indicate hierarchical relationships
The forward-slash (/) character is used in the path portion of the URI to indicate a hierarchical relationship between resources. e.g.
http://api.example.com/device-management
http://api.example.com/device-management/managed-devices
http://api.example.com/device-management/managed-devices/{id}
http://api.example.com/device-management/managed-devices/{id}/scripts
http://api.example.com/device-management/managed-devices/{id}/scripts/{id}
2.2.2. Do not use trailing forward slash (/) in URIs
As the last character within a URI’s path, a forward slash (/) adds no semantic value and may confuse. It’s better to drop it from the URI.
http://api.example.com/device-management/managed-devices/
http://api.example.com/device-management/managed-devices /*This is much better version*/
2.2.3. Use hyphens (-) to improve the readability of URIs
To make your URIs easy for people to scan and interpret, use the hyphen (-) character to improve the readability of names in long-path segments.
http://api.example.com/devicemanagement/manageddevices/
http://api.example.com/device-management/managed-devices /*This is much better version*/
2.2.4. Do not use underscores ( _ )
It’s possible to use an underscore in place of a hyphen to be used as a separator – But depending on the application’s font, it is possible that the underscore (_) character can either get partially obscured or completely hidden in some browsers or screens.
To avoid this confusion, use hyphens (-) instead of underscores ( _ ).
http://api.example.com/inventory-management/managed-entities/{id}/install-script-location //More readable
http://api.example.com/inventory-management/managedEntities/{id}/installScriptLocation //Less readable
2.2.5. Use lowercase letters in URIs
When convenient, lowercase letters should be consistently preferred in URI paths.
http://api.example.org/my-folder/my-doc //1
HTTP://API.EXAMPLE.ORG/my-folder/my-doc //2
http://api.example.org/My-Folder/my-doc //3
In the above examples, 1 and 2 are the same, but 3 is not as it uses My-Folder in capital letters.
2.3. Do not use file extensions
File extensions look bad and do not add any advantage. Removing them decreases the length of URIs as well. No reason to keep them.
Apart from the above reason, if you want to highlight the media type of API using file extension, then you should rely on the media type, as communicated through the Content-Type header, to determine how to process the body’s content.
http://api.example.com/device-management/managed-devices.xml /*Do not use it*/
http://api.example.com/device-management/managed-devices /*This is correct URI*/
2.4. Never use CRUD function names in URIs
We should not use URIs to indicate a CRUD function. URIs should only be used to uniquely identify the resources and not any action upon them.
We should use HTTP request methods to indicate which CRUD function is performed.
HTTP GET http://api.example.com/device-management/managed-devices //Get all devices
HTTP POST http://api.example.com/device-management/managed-devices //Create new Device
HTTP GET http://api.example.com/device-management/managed-devices/{id} //Get device for given Id
HTTP PUT http://api.example.com/device-management/managed-devices/{id} //Update device for given Id
HTTP DELETE http://api.example.com/device-management/managed-devices/{id} //Delete device for given Id
2.5. Use query component to filter URI collection
Often, you will encounter requirements where you will need a collection of resources sorted, filtered, or limited based on some specific resource attribute.
For this requirement, do not create new APIs – instead, enable sorting, filtering, and pagination capabilities in resource collection API and pass the input parameters as query parameters. e.g.
http://api.example.com/device-management/managed-devices
http://api.example.com/device-management/managed-devices?region=USA
http://api.example.com/device-management/managed-devices?region=USA&brand=XYZ
http://api.example.com/device-management/managed-devices?region=USA&brand=XYZ&sort=installation-date
Resource versioning
To manage this complexity, version your API. Versioning helps us to iterate faster when the needed changes are identified in the APIs.
Change in an API is inevitable as our knowledge and experience of a system improve. Managing the impact of this change can be quite a challenge when it threatens to break existing client integration.
1. When to version?
APIs only need to be up-versioned when a breaking change is made.
Breaking changes include:
- a change in the format of the response data for one or more calls
- a change in the request or response type (i.e. changing an integer to a float)
- removing any part of the API.
Breaking changes should always result in a change to the major version number for an API or content response type.
Non-breaking changes, such as adding new endpoints or new response parameters, do not require a change to the major version number.
However, it can be helpful to track the minor versions of APIs when changes are made to support customers who may be receiving cached versions of data or might be experiencing other API issues.
2. How to version a REST API?
REST doesn’t provide for any specific versioning guidelines, but the more commonly used approaches fall into three categories:
2.1. URI Versioning
Using the URI is the most straightforward approach (and most commonly used as well) though it does violate the principle that a URI should refer to a unique resource. You are also guaranteed to break client integration when a version is updated.
http://api.example.com/v1
http://apiv1.example.com
The version need not be numeric, nor specified using the “v[x]” syntax.
Alternatives include dates, project names, seasons, or other identifiers that are meaningful enough to the team producing the APIs and flexible enough to change as the versions change.
2.2. Versioning using Custom Request Header
A custom header (e.g. Accept-version) allows you to preserve your URIs between versions though it is effectively a duplicate of the content negotiation behavior implemented by the existing Accept header.
Accept-version: v1
Accept-version: v2
2.3. Versioning using “Accept” header
Content negotiation may let you preserve a clean set of URLs, but you still have to deal with the complexity of serving different versions of content somewhere.
This burden tends to be moved up the stack to your API controllers which become responsible for figuring out which version of a resource to send.
The result tends to be a more complex API as clients have to know which headers to specify before requesting a resource.
Accept: application/vnd.example.v1+json
Accept: application/vnd.example+json;version=1.0
In the real world, an API is never going to be completely stable. So it’s important how this change is managed.
A well-documented and gradual deprecation of API can be an acceptable practice for most APIs.
REST API Security Essentials
REST API Security isn’t an afterthought. It has to be an integral part of any development project and also for REST APIs.
There are multiple ways to secure a RESTful API e.g. basic auth, OAuth, etc. but one thing is sure that RESTful APIs should be stateless – so request authentication/authorization should not depend on sessions.
Instead, each API request should come with some sort of authentication credentials that must be validated on the server for every request.
1. REST Security Design Principles
The paper “The Protection of Information in Computer Systems” by Jerome Saltzer and Michael Schroeder, put forth eight design principles for securing information in computer systems, as described in the following sections:
- Least Privilege: An entity should only have the required set of permissions to perform the actions for which they are authorized, and no more. Permissions can be added as needed and should be revoked when no longer in use.
- Fail-Safe Defaults: A user’s default access level to any resource in the system should be “denied” unless they’ve been granted a “permit” explicitly.
- The economy of Mechanism: The design should be as simple as possible. All the component interfaces and the interactions between them should be simple enough to understand.
- Complete Mediation: A system should validate access rights to all its resources to ensure that they’re allowed and should not rely on the cached permission matrix. If the access level to a given resource is being revoked, but that isn’t reflected in the permission matrix, it would violate the security.
- Open Design: This principle highlights the importance of building a system in an open manner—with no secret, confidential algorithms.
- Separation of Privilege: Granting permissions to an entity should not be purely based on a single condition, a combination of conditions based on the type of resource is a better idea.
- Least Common Mechanism: It concerns the risk of sharing state among different components. If one can corrupt the shared state, it can then corrupt all the other components that depend on it.
- Psychological Acceptability: It states that security mechanisms should not make the resource more difficult to access than if the security mechanisms were not present. In short, security should not make worse the user experience.
2. Best Practices to Secure REST APIs
Below given points may serve as a checklist for designing the security mechanism for REST APIs.
2.1. Keep it Simple
Secure an API/System – just how secure it needs to be. Every time you make the solution more complex “unnecessarily,” you are also likely to leave a hole.
2.2. Always Use HTTPS
By always using SSL, the authentication credentials can be simplified to a randomly generated access token. The token is delivered in the username field of HTTP Basic Auth. It’s relatively simple to use, and you get a lot of security features for free.
If you use HTTP 2, to improve performance – you can even send multiple requests over a single connection, that way you avoid the complete TCP and SSL handshake overhead on later requests.
2.3. Use Password Hash
Passwords must always be hashed to protect the system (or minimize the damage) even if it is compromised in some hacking attempts. There are many such hashing algorithms that can prove really effective for password security e.g. PBKDF2, bcrypt, and scrypt algorithms.
2.4. Never expose information on URLs
Usernames, passwords, session tokens, and API keys should not appear in the URL, as this can be captured in web server logs, which makes them easily exploitable.
https://api.domain.com/user-management/users/{id}/someAction?apiKey=abcd123456789 //Very BAD !!
The above URL exposes the API key. So, never use this form of security.
2.5. Consider OAuth
Though basic auth is good enough for most of the APIs and if implemented correctly, it’s secure as well – yet you may want to consider OAuth as well.
The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its behalf.
2.6. Consider Adding Timestamp in Request
Along with other request parameters, you may add a request timestamp as an HTTP custom header in API requests.
The server will compare the current timestamp to the request timestamp and only accepts the request if it is after a reasonable timeframe (30 seconds, perhaps).
This will prevent very basic replay attacks from people who are trying to brute force your system without changing this timestamp.
2.7. Input Parameter Validation
Validate request parameters on the very first step, before it reaches application logic. Put strong validation checks and reject the request immediately if validation fails.
In API response, send relevant error messages and examples of correct input format to improve user experience.
Resources
-
https://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
An API is a user interface for a developer - so put some effort into making it pleasant Use RESTful URLs and actions Use SSL everywhere, no exceptions An API is only as good as its documentation - so have great documentation Version via the URL, not via headers Use query parameters for advanced filtering, sorting & searching Provide a way to limit which fields are returned from the API Return something useful from POST, PATCH & PUT requests HATEOAS isn’t practical just yet Use JSON where possible, XML only if you have to You should use camelCase with JSON, but snake_case is 20% easier to read Pretty print by default & ensure gzip is supported Don’t use response envelopes by default Consider using JSON for POST, PUT and PATCH request bodies Paginate using Link headers Provide a way to autoload related resource representations Provide a way to override the HTTP method Provide useful response headers for rate limiting Use token based authentication, transported over OAuth2 where delegation is needed Include response headers that facilitate caching Define a consumable error payload Effectively use HTTP Status codes