There are three main stages to adding an API class:
Within the src/Services/
directory you should find a number of files. Most notable for us is the Service.ts
and Services.ts
files.
Service.ts
is the abstract class from which all our "Service" classes extend. It is responsible for the construction of the class, for handling the classes events, and holding memory references to the config bag and the HTTP client.
Within src/Services
create a new file with the same name as the entity it will host. For example, Example.ts
should look like this:
import Service from './Service';/*** Example class*/class Example extends Service{}export default Example;
Remember src/Services/Services.ts
? This is the first step of building the resolution root for our new class.
Services.ts
exports a TypeScript namespace, which allows use to easily import all our "Services" into our main class.
Firstly, import your newly created "Service" class.
diff a/src/Services/Services.ts b/src/Services/Services.tsimport Accounts from './Accounts';+import Example from './Example';
Then, you should add the imported class to the end of the export
object.
diff a/src/Services/Services.ts b/src/Services/Services.tsAccounts,-}+ Example,+}
Next, you need to register your singleton. This will ensure configuration and state persistence.
This is where things start to come together. Locate src/index.ts
and open it. Inside here, you will find our root object. It is the home of our application container.
The first step here is to register a singleton for your "Service" class.
Find the boot
method, and go to the bottom of that method. Here we are going to add a binding that the container will use to build a single instance of your class.
this.singleton('services.example',(app: ContainerInterface, config: Config) => {return new Services.Example(config,app.resolve('client'));});
The counterpart to this registration is the resolution of its abstract definition; here 'services.example'
.
Near the bottom of the root Xedi
class, add the following function.
static get Example(): Services.Example{return this.resolveInstance().resolve('services.example');}
Congratulations! You can now resolve you new class. If you want, go ahead and jump into the playground and type Xedi.Example
. It should resolve you an instance of your new class and inside, you will find a config bag and a HTTP client.
You want to start giving your class some abilities. Depending on your situation you might be adding a series of CRUD endpoints, or something entirely different. In most cases, you will be sending HTTP requests to an instance of the XEDI API Gateway. Most endpoints are the same, but there will be a few caveats and edge-cases. Consider the following examples:
list() {return this.client.get<JsonResponse<Example>>(`1/examples`).then((response: AxiosResponse<JsonResponse<Example>>) => {return response.data.data;});}
An indexing method
Lets look at line three of the above example. In JavaScript this would look like .get('1/examples')
, so whats the extra stuff? This how we tell TypeScript, what we expect the HTTP Client to return in the promise. Here we tell it we want a JsonResponse
which itself, must contain an Example
.
Example
?In this instance, Example is a model. A model is a simple definition of the data structure we expect. If you aren't familiar with this concept, read the section on models.
On line four, we see that our response expectation is defined again, but this time within an AxiosResponse
. Translated to plain english this reads: Where response
is a JSON representation of a Example within an AxiosResponse. Again, at runtime, this compiles down to nothing, but here in TypeScript it helps us understand the intent of the code.