hal-http-client
v0.12.1
Published
A status code driven JSON HAL HTTP client based on the fetch API.
Downloads
1,535
Readme
hal-http-client
A status code driven JSON HAL HTTP client based on the fetch API.
The API doc can be found here.
What is HAL and where does this client come into play?
HAL (Hypertext Application Layer), as the name already states, is an application level layer on top of JSON or XML that provides conventions for linking between different resources. For RESTful APIs it helps to reach level 3 of the Richardson Maturity Model . Additionally it defines how to embed related resources into the response of their parent resource for efficient APIs and lowering the need for actual HTTP requests over the wire.
The hal-http-client defines a convenient API for accessing resources represented as HAL on top of JSON (XML is currently not supported and there are no plans in doing so):
- It allows navigating through resources with
application/hal+json
media type by following the available relations - If a resource is embedded, it transparently reads the embedded representation instead of making a real HTTP request
- Flow control is supported by providing a status code driven API
Templated URLs (RFC6570) are supported via the url-template library.
Installation and Usage
npm install hal-http-client
Basically import the library, make a new instance and start discovering resources by making a GET
request to the root URL of your API.
import * as halHttp from '../hal-http-client';
const hal = halHttp.create( {
headers: { 'accept-language': 'de' },
on: {
// globally define how responses for status codes should be handled that were not handled locally
'5xx': ( data, response ) => {
console.error( `Caught unhandled 5xx error (status: ${response.status})` );
}
}
} );
hal.get( 'http://my-server/api/root' )
// See the example below on how to go on from here
Example
Lets take the following simple model of a person and his cars from the specs:
// person
{
"name": "Peter",
"age": 34
}
// his address
{
"street": "Mainstreet 12",
"postalCode": "12345",
"city": "Faketown"
}
// his cars
[
{
"type": "VW",
"model": "T3 Vanagon"
},
{
"type": "DMC",
"model": "DeLorean"
}
]
An according JSON response with all sub resources embedded could look like this:
{
"name": "Peter",
"age": 34,
"_links": {
"self": { "href": "peter" },
"address": { "href": "peter/address" },
"cars": { "href": "peter/cars" }
},
"_embedded": {
"address": {
"street": "Mainstreet 12",
"postalCode": "12345",
"city": "Faketown",
"_links": { "self": { "href": "peter/address" } }
},
"cars": {
"_links": {
"self": { "href": "peter/cars" },
"car": [ { "href": "peter/cars/0" }, { "href": "peter/cars/1" } ]
},
"_embedded": {
"car": [
{
"type": "VW",
"model": "T3 Vanagon",
"_links": { "self": { "href": "peter/cars/0" } }
},
{
"type": "DMC",
"model": "DeLorean",
"_links": { "self": { "href": "peter/cars/1" } }
}
]
}
}
}
}
Getting the cars starting from the person can be achieved in the following way:
hal.get( '/peter' )
.on( {
'200': hal.thenFollow( 'cars' )
} )
.on( {
'200': hal.thenFollowAll( 'car' )
} )
.on( {
'200': carList => {
console.log( carList );
// will print:
/*
[
{
"type": "VW",
"model": "T3 Vanagon",
"_links": { "self": { "href": "peter/cars/0" } }
},
{
"type": "DMC",
"model": "DeLorean",
"_links": { "self": { "href": "peter/cars/1" } }
}
]
*/
}
} );
The hal.thenFollow
method is a convenience factory for creating a function, which takes the response of the previous request, then follows the given relation in the response body and finally returns a ResponsePromise
for further on
chaining.
So instead this part could have been written the following way:
hal.get( '/peter' )
.on( {
'200': ( peterResource, response ) => {
return hal.follow( peterResource, 'cars' );
}
} )
The method hal.thenFollowAll
basically works the same, only that it expects to find an array of relations to follow on the given resource and return all results again as an array.
Optional relations
For some resources a relation may be optional.
If it is sufficient to just check whether the relation is available or not, the function halHttp.canFollow()
is the right choice.
Simply pass it the representation as first and the relation to test as second argument:
halHttp.canFollow( peterResource, 'cars' ); // => true
halHttp.canFollow( peterResource, 'job' ); // => false
If on the other hand two different tasks should be executed in either case, the "virtual" status code 'norel'
can instead be used as an on
-handler:
hal.follow( peterResource, 'work' )
.on( {
'200': () => {
console.log( 'Peter currently is employed' );
},
'norel': () => {
console.log( 'Peter currently is unemployed' );
}
} );
From here on you should consult the API doc to have a look at all the other APIs available on the hal-http-client.