Api
The api package is a layer above the http package, that allows you to create reusable / pre-configured requests.
It also comes with decorators that integrates the http layer to models.
You can import it from @banquette/api
.
NOTE
This part of the documentation requires that you are familiar with the Http service and with models (for the decorators part).
Endpoint
An endpoint is, in essence, just an object that holds the configuration of a Http request for future use. It does a bit more than that but the general idea is to separate the configuration of your requests from their usage.
Here is an example:
const endpoint = new ApiEndpoint({
url: '/article/create',
method: HttpMethod.POST
});
From this endpoint, you can generate an HttpRequest
:
const request: HttpRequest = endpoint.buildRequest({
title: '...',
content: '...'
});
// You can then call HttpService::send() with it.
Url parameters
Where the Http service expects the url to be ready to use, the endpoints can manage dynamic parameters.
It works in two parts. First, in the endpoint, you define what parameters are allowed. This is done via the params
attribute:
const endpoint = new ApiEndpoint({
url: '/articles',
params: {
page: {required: true}
}
});
As you can see the params
attribute expects an object, where the keys are the parameters' names and the value defines the configuration.
Here we define that this endpoint must have a page
parameter defined when building the request:
endpoint.buildRequest(null /* payload */, {page: 2} /* The page parameter */);
Url parameters validation
For each parameter you can define 4 configuration options:
Name | Description |
---|---|
required | true to make the parameter required. Default is false . |
validator | Any validator to use to validate the value when building the request. Default is null . |
defaultValue | The value to use if the parameter is not defined when building the request. |
url | If true , prevent parameters to be added to the query string if no placeholder is found in the url.More info here. |
TIP
You can also set the configuration to null
to use the default value for each option:
const endpoint = new ApiEndpoint({
url: '/articles',
params: {
// Here the page parameter is allowed, with no validation or default value.
page: null
}
});
Another shorthand is to set the configuration to true
:
const endpoint = new ApiEndpoint({
url: '/articles',
params: {
// Here the page parameter is required, but can take any value.
page: true
}
});
Three types of exceptions can be thrown if your parameters doesn't match the configuration:
MissingRequiredParameterException
: a required parameter is missing,InvalidParameterException
: a parameter failed to validate,UnsupportedParametersException
: a parameters not allowed by the configuration have been given as input.
TIP
You can allow use the wildcard parameter *
to define the configuration to apply to any undefined parameter:
import { IsType, Type } from '@banquette/validation';
const endpoint = new ApiEndpoint({
url: '/articles',
params: {
// Here the "page" parameter must be a number, and has a default value of 1.
page: {validator: IsType(Type.Number), defaultValue: 1},
// Any other parameter is allowed, with no restrictions.
'*': null
}
});
Url parameters placeholders
When an url parameter is given to the buildRequest()
method, it will first search for a placeholder in the url string.
It looks like this:
/article/edit/{id}
Here {id}
is a placeholder that will be replaced when building the final url, if the parameter is defined.
NOTE
If the parameter is not defined when building the request, the placeholder is kept as is, in the url.
The {
and }
will be url encoded through.
If the parameter is defined when building the request, and no placeholder is found, it will be added to the query string:
const endpoint = new ApiEndpoint({
url: '/article/edit/{id}',
params: {
// "id" is required.
id: true,
// Allow anything else.
'*': null
}
});
const request: HttpRequest = endpoint.buildRequest(null, {id: 12, other: 'test'});
console.log(request.staticUrl);
// /article/edit/12?other=test
Full configuration
Here is the full configuration of an endpoint:
interface ApiEndpointOptions {
/**
* The url of the endpoint.
* The url can contain variables, surrounded by brackets "{" and "}":
* e.g.?: /user/{id}
*/
url: string;
/**
* The HTTP method to use when calling the endpoint.
*
* Default method is GET.
*/
method?: HttpMethod;
/**
* Allowed url parameters.
* Each parameter can have a validator associated with it to make it required or validate its value.
*
* If a parameter not present in the map is given as input when building the url, an exception
* will be thrown.
*
* To allow any parameter you can use the wildcard ("*") parameter name.
*/
params?: Record<string, ApiEndpointParameterOptions>;
/**
* Headers to include when building the request.
*/
headers?: Record<string, string>;
/**
* Type of encoder to use when building the request.
*/
payloadType?: symbol;
/**
* Type of decoder to process the response.
*/
responseType?: symbol;
/**
* Maximum duration of the request (in milliseconds).
*/
timeout?: number|null;
/**
* If true, cookies and auth headers are included in the request.
*/
withCredentials?: boolean|null;
/**
* MimeType of the payload.
*/
mimeType?: string|null;
/**
* Maximum number of tries allowed for the request.
*/
retry?: number|null;
/**
* Time to wait before trying again in case of error.
*/
retryDelay?: number|'auto'|null;
/**
* The higher the priority the sooner the request will be executed when the queue contains multiple requests.
*/
priority?: number|null;
/**
* Tags that will be sent with emitted events.
*/
tags?: symbol[];
/**
* Any additional data that will be added to the request.
*/
extras?: Record<string, any>;
}
Store your endpoints
When you create an endpoint manually like we've seen above, you're responsible for storing it. An endpoint just an object like any other.
But you can also store it in the ApiEndpointStorageService
service:
import { ApiEndpointStorageService, ApiEndpoint } from "@banquette/api";
import { Injector } from "@banquette/dependency-injection";
import { HttpMethod } from "@banquette/http";
const storage = Injector.Get(ApiEndpointStorageService);
storage.registerEndpoint({
name: 'create_article', // Name of the endpoint
// Other values are the same as seen before.
url: '/articles',
method: HttpMethod.POST
});
// Shorthand
storage.registerEndpoint('create_article', '/articles', HttpMethod.POST);
// Or
storage.registerEndpoint('create_article', new ApiEndpoint(...));
You can then get it:
const endpoint: ApiEndpoint = storage.registerEndpoint('create_article');
Test if it exists:
storage.hasEndpoint('create_article'); // true
Remove it:
storage.removeEndpoint('create_article');
Or remove all endpoints:
storage.clear();
For all these methods, you can add an optional parameter ctor
which expects a reference to the constructor of a model.
We'll see that in the next section.
Build requests manually
You can also create ApiRequest
objects manually and give them to the send()
method of the service. You have two choices for that:
1) The request builder:
It extends the request builder of the package http, so it works exactly the same.
The only difference is that it adds a endpoint
and model
methods to assign a endpoint name and model identifier respectively.
import { ApiRequestBuilder } from '@banquette/api';
ApiRequestBuilder.Create()
.endpoint('create')
.model(Article)
.payload({title: '...'})
.getRequest();
2) The request factory:
Just like the builder, the factory works the same as the one in the http package
with the addition of the endpoint
and model
attributes:
import { ApiRequestFactory } from '@banquette/api';
ApiRequestFactory.Create({
endpoint: 'create',
model: Article,
payload: {title: '...'}
});