Skip to main content

Create HTTP client

So far we are developing a very boring application. To display information about pokemon in our Pokedex, we need to get data from the pokeapi API. In this lesson we will learn how to create API clients and work with the Dependency Injection container.

info

Inversion of Control, and Dependency Injection (DI) are hard enough concepts to understand if you're dealing with them for the first time. But using DI makes our applications incredibly flexible, extensible and modular. The DI in tramvai is inspired by the dependency injection system in Angular.

Module @tramvai/module-http-client provides a factory for creating HTTP clients.

Important point! tramvai is built on the principle of dependenciy injection, so the library does not export the factory directly, but adds it to the DI container of the application by a special token as a key. The DI allows you to construct the application from modules as if they were separate LEGO blocks.

⌛ Install the library @tramvai/module-http-client:

npx tramvai add @tramvai/module-http-client

⌛ Connect @tramvai/module-http-client into the application:

index.ts
import { HttpClientModule } from '@tramvai/module-http-client';

createApp({
name: 'pokedex',
modules: [
...modules,
HttpClientModule,
],
providers: [...providers],
actions: [...actions],
bundles: {...bundles},
});

Now we can create an API client specifically for pokeapi, let's make it a separate tramvai module right away to demonstrate module and DI capabilities.

⌛ Create a file shared/api/index.ts with an empty module:

shared/api/index.ts
import { Module } from '@tramvai/core';

@Module({
providers: [],
})
export class PokeApiModule {}

It is recommended to add base urls for different APIs to application env variables via DI - this is useful for testing and mocking, and is consistent with 12-factor application.

⌛ Add support for the new env variable in PokeApiModule with the ENV_USED_TOKEN token:

shared/api/index.ts
import { Module, provide } from '@tramvai/core';
import { ENV_USED_TOKEN } from '@tramvai/module-common';

@Module({
providers: [
provide({
provide: ENV_USED_TOKEN,
multi: true,
useValue: [
{
key: 'POKEAPI_BASE_URL',
optional: true,
// default value
value: 'https://pokeapi.co/api/v2/',
},
],
}),
]
})
export class PokeApiModule {}

In value property we immediately added a default value. This value can be overridden in the env.development.js file when developing locally, or via environment variables when running the tramvai application on the server.

To add a new provider to the DI, in this case a new HTTP client, you need two things:

  • Create a token with the interface of the new HTTP client, with HttpClient as the interface
  • Create provider with the implementation of this token, using the factory HTTP_CLIENT_FACTORY

⌛ Create a token for the new HTTP client:

shared/api/index.ts
import { Module, provide, createToken } from '@tramvai/core';
import { ENV_USED_TOKEN } from '@tramvai/module-common';
import { HttpClient } from '@tramvai/module-http-client';

export const POKEAPI_HTTP_CLIENT = createToken<HttpClient>(
'pokeapi HTTP client'
);

@Module({
providers: [
provide({
provide: ENV_USED_TOKEN,
multi: true,
useValue: [
{
key: 'POKEAPI_BASE_URL',
optional: true,
value: 'https://pokeapi.co/api/v2/',
},
],
}),
]
})
export class PokeApiModule {}

The POKEAPI_HTTP_CLIENT token can be used simultaneously as a key in the DI, and as an interface, with typeof - typeof POKEAPI_HTTP_CLIENT

⌛ Create a provider with an implementation of the POKEAPI_HTTP_CLIENT token:

shared/api/index.ts
import { Module, provide, createToken } from '@tramvai/core';
import { ENV_USED_TOKEN, ENV_MANAGER_TOKEN } from '@tramvai/module-common';
import { HttpClient, HTTP_CLIENT_FACTORY } from '@tramvai/module-http-client';

export const POKEAPI_HTTP_CLIENT = createToken<HttpClient>('pokeapi HTTP client');

@Module({
providers: [
provide({
provide: POKEAPI_HTTP_CLIENT,
// what the useFactory call will return will be written to the DI,
// and the dependency types will be derived automatically from the deps
useFactory: ({ factory, envManager }) => {
return factory({
name: 'pokeapi',
// используем базовый урл pokeapi из env переменной
baseUrl: envManager.get('POKEAPI_BASE_URL'),
});
},
// all dependencies from deps will be taken from DI and passed to useFactory
deps: {
factory: HTTP_CLIENT_FACTORY,
envManager: ENV_MANAGER_TOKEN,
},
}),
provide({
provide: ENV_USED_TOKEN,
multi: true,
useValue: [
{
key: 'POKEAPI_BASE_URL',
optional: true,
value: 'https://pokeapi.co/api/v2/',
},
],
}),
]
})
export class PokeApiModule {}
tip

A unique instance of POKEAPI_HTTP_CLIENT will be created for each user request to the application, allowing you to centralized add user data to the request parameters, for example you can take the User-Agent of the user and add it to the headers of all API requests. In doing so, all these POKEAPI_HTTP_CLIENT instances will have a shared in-memory cache. This cache can be disabled, for example for integration tests, with the env variable HTTP_CLIENT_CACHE_DISABLED=true.

⌛ Connect PokeApiModule into the application:

index.ts
import { HttpClientModule } from '@tramvai/module-http-client';
import { PokeApiModule } from '~shared/api';

createApp({
name: 'pokedex',
modules: [
...modules,
HttpClientModule,
PokeApiModule,
],
providers: [...providers],
actions: [...actions],
bundles: {...bundles},
});

Now we have a ready HTTP client that can be used in components, actions and other providers!

Next lesson