Tramway HATEAOS Formatter is a request formatter that can be used with the Tramway Router to create a HAL/HATEAOS formatted REST API. It includes:
- A HATEAOSFormatter for requests.
- A MimeTypeResolver to add formats for different content-type headers.
- A paginated variant to handle pagination.
npm install --save tramway-formatter-hateaos
- Requires
tramway-core-router
version 3.3.0+ to be already installed.
With dependency injection you can add the following entries to your services config files.
Add the libbrary service declarations to src/config/services/index.js
import { services as hateoas } from 'tramway-formatter-hateaos';
export default {
...hateoas,
}
Then, add the service declaration for your RestfulController
in src/config/services/controllers.js
import {
SampleController,
} from '../../controllers';
export default {
"controller.sample": {
"class": SampleController,
"constructor": [
{"type": "service", "key": "router"},
{"type": "service", "key": "service.sample"},
{"type": "service", "key": "hateoas.service.formatter"},
],
"functions": []
},
};
For paginated formatter, use hateoas.service.formatter:paginated
instead of hateoas.service.formatter
.
If you intend to use the library to process responses from APIs using HATEAOS format, add the Provider decorator to your src/config/services/providers.js
:
import {providers} from 'tramway-formatter-hateaos';
const {HATEAOSAPIProvider} = providers;
export default {
"provider.hateaos": {
"class": HATEAOSAPIProvider,
"constructor": [
{"type": "service", "key": "provider.api"},
{"type": "service", "key": "factory.hateaos"},
{"type": "parameter", "key": "api_resource"},
],
"functions": []
}
}
Then in the repository where you'd normally use provider.api
, use provider.hateaos
instead. Note that this requires you set up the tramway-connection-rest-api
and set up the provider for it as indicated by provider.api
. The api_resource
parameter is a string with the name of the key under the _embedded
object of the response.
In a RestfulController
that has access to the Formatter you can:
Format an entity
let formattedItem = this.formatter.formatEntity(entity);
Format a collection
let formattedItem = this.formatter.formatCollection(collection)
To send a response with the formatted item.
this.formatter.send(res, formattedItem, options);
In an API context, you can process REST API responses that use HATEAOS by using the included HATEAOSAPIProvider
decorator on the APIProvider
. This will automatically convert all HATEAOS responses from the host API to the standard Collection
s and Entity
s that are expected from the Provider
.
The HATEAOSFormatter
will automatically generate entities and collections with their respective self and paginated properties. However, more complex APIs will have additional links for other resources and sub-resources.
The HATEAOSEntity
and HATEAOSCollection
both have addLink
methods which let you assign a link and label as part of this metadata. However, in most cases, these objects will not be accessible in the controller action since the formatting has been abstracted at that point.
To solve this problem, the send
method now includes a third parameter to pass options. The HATEAOSFormatter
takes a links
option which will accept an array of the following form and add each link to the links of the respective item.
[
{"label": "subresources", "link": "subresources", "formatted": false}
]
The link itself is intended to be relative so in the case of a subresource, the url formatting will just append the link. In more custom situations, the link can be given an absolute url and it will be used as long as formatted
is set to true.
When using the additional links with the RestfulController
, it will not be possible to access the action directly so in that case the aforementioned configuration would need to be assigned to the route in the configuration, except in cases where a controller action is custom.
{
"arguments": ["id"],
"methods": ["get"],
"path": "resource",
"controller": "controllers.resource",
"action": "getOne",
"links": [
{"label": "subresources", "link": "subresources", "formatted": false},
{"label": "custom", "link": "http://localhost/resource/1/custom/5", "formatted": true}
]
},
This routing config will produce the following result when called GET http://localhost/resource/1
:
{
"id": 1,
"_links": {
"self": {
"href": "http://localhost/resource/1"
},
"subresources": {
"href": "http://localhost/resource/1/subresources"
},
"custom": {
"href": "http://localhost/resource/1/custom/5"
}
}
}
In addition, additional resources can be embedded in the response by means of using the setEmbedded
method on any HATEOASItem
or by passing the corresponding object as an option to the HATEAOSFormatter
's send
method.
The following code snippet will generate the following output for GET http://localhost/resource/1
.
hateaosFormatter.send(res, hateaosFormatter.formatEntity(item), {
links: [
{"label": "subresources", "link": "subresources", "formatted": false},
{"label": "custom", "link": "http://localhost/resource/1/custom/5", "formatted": true}
],
embedded: {
"resource2": {
id: 1
}
}
})
Will produce:
{
"id": 1,
"_links": {
"self": {
"href": "http://localhost/resource/1"
},
"subresources": {
"href": "http://localhost/resource/1/subresources"
},
"custom": {
"href": "http://localhost/resource/1/custom/5"
}
},
"_embedded": {
"resource2": {
"id": 1,
}
}
}
The HATEAOSFormatter
can take additional parameters: MimeTypeResolver, UrlGenerator
. Both of these are automatically set if they aren't explicitly added to the constructor arguments. The MimeTypeResolver
handles the output for different content-type headers. The UrlGenerator
handles the generation of urls for the HATEAOS decorators' links.