Axiosfit in a real project - Arango Of Thrones
Tags: typescript, arangodb, nestjs
Published: 2020-03-02 11:04:02

> [Original post](https://blog.yggdrasilts.com/arango-of-thrones/)

In a previous [post](https://blog.yggdrasilts.com/axiosfit-a-cool-way-to-organize-your-http-client-requests/), I told about the [YggdrasilTS](https://github.com/yggdrasilts) flagship, [Axiosfit](https://github.com/yggdrasilts/axiosfit). Now, it turns to show a sample in a real project.

Although [Axiosfit](https://github.com/yggdrasilts/axiosfit) is yet in the early version, the last version has enough features to be used in a real project and start testing how it works. Let's begin...

The project we are going to build is a [NestJS](https://nestjs.com/) application that loads the Game Of Thrones data inside an [ArangoDB](https://www.arangodb.com/) database using services built with [Axiosfit](https://github.com/yggdrasilts/axiosfit).

> 'INFO' This data is extracted from one of the [ArangoDB examples datasets](https://github.com/arangodb/example-datasets/tree/master/GameOfThrones) and if you want to practice a little bit with [ArangoDB](https://www.arangodb.com/), [here](https://www.arangodb.com/docs/stable/aql/tutorial.html) you can find the tutorial.

The first step is to create a [NestJS](https://nestjs.com/) application. To do so, you can follow its [documentation](https://docs.nestjs.com/first-steps) and create the application using its client with the name ***arangodb-axiosfit,*** or whatever name you want. After this, you can open the project with your best editor or IDE and you will see something like the following image:

![](https://blog.yggdrasilts.com/content/images/2020/02/image-1.png)

Like its [documentation](https://docs.nestjs.com/first-steps) sais, you can execute `npm start` and you will see how it works.

Now, we are going to adapt the application adding the [Axiosfit](https://github.com/yggdrasilts/axiosfit) library and create all the needed resources.

To add [Axiosfit](https://github.com/yggdrasilts/axiosfit), we only need to execute `npm i @yggdrasilts/axiosfit`. After this, we are going to see the [Axiosfit](https://github.com/yggdrasilts/axiosfit) dependency in our *package.json* file with the latest version published:

```json
...
"dependencies": {
  "@nestjs/common": "^6.10.14",
  "@nestjs/core": "^6.10.14",
  "@nestjs/platform-express": "^6.10.14",
  "@yggdrasilts/axiosfit": "^0.8.0", <- Axiosfit dependency
  "reflect-metadata": "^0.1.13",
  "rimraf": "^3.0.0",
  "rxjs": "^6.5.4"
},
...
```

Before continue, I would like to talk about how we are going to use [ArangoDB](https://www.arangodb.com/). If you want, you can install it normally using its [documentation](https://www.arangodb.com/docs/stable/installation.html) but, for the purpose of this example, we are going to use the [ArangoDB docker container](https://www.arangodb.com/download-major/docker/) using T[estContainers](https://www.testcontainers.org/) library.

> 'INFO' What is T[estContainers](https://www.testcontainers.org/)? This library has been thought to support tests, providing lightweight, throwaway instances of common databases or anything else that can run in a [Docker](https://www.docker.com/) container.

In this case, it is not for testing purpose but T[estContainers](https://www.testcontainers.org/) is an easy way to manage docker containers through the code.

In their web, T[estContainers](https://www.testcontainers.org/) shows how to be used with java but also it has its own [nodejs](https://nodejs.org/en/) library, [testscontainers-node](https://github.com/testcontainers/testcontainers-node), that we will use in the project. To install it, we only need to execute the following command: `npm i -D testcontainers`.

> 'INFO' [Docker](https://docs.docker.com/install/) is a requirement if you want to use [TestContainers](https://www.testcontainers.org/).

Once installed, we will create a `setup.ts` file inside `test` folder where we will instantiate [ArangoDB](https://www.arangodb.com/) docker image with T[estContainers](https://www.testcontainers.org/).

```typescript
import { GenericContainer } from 'testcontainers';
import { StartedTestContainer } from 'testcontainers/dist/test-container';
import logger from 'testcontainers/dist/logger';

let mockServer: StartedTestContainer;

export const stopMockServer = async () => {
  await mockServer.stop();
  logger.info('Mock Server stopped.');
};

export const startMockServer = async (port: number = 8529): Promise<StartedTestContainer> => {
  let arangoDBserverIP: string;
  let arangoDBserverPort: string;

  try {
    const container = await new GenericContainer('arangodb', '3.6.0');

    mockServer = await container
      .withExposedPorts(port)
      .withEnv('ARANGO_NO_AUTH', '1')
      .start();

    arangoDBserverIP = await mockServer.getContainerIpAddress();
    arangoDBserverPort = String(await mockServer.getMappedPort(port));
    const url = `http://${arangoDBserverIP}:${arangoDBserverPort}`;
    logger.info(url);
    process.env.MOCK_SERVER_URL = url;

    return mockServer;
  } catch (error) {
    logger.error(error);
    await stopMockServer();
    throw new Error(error);
  }
};

export default async () => await startMockServer();
```

Finally, we are going to modify our `main.ts` file to initialize the [ArangoDB](https://www.arangodb.com/) with our [NestJS](https://nestjs.com/) application.

```typescript
import { NestFactory } from '@nestjs/core';
import { INestApplication, Logger } from '@nestjs/common';

import { startMockServer, stopMockServer } from '../test/setup';

import { AppModule } from './app.module';

let baseUrl = '';
const port = process.env.PORT || 3000;
let app: INestApplication;
const logger = new Logger('Bootstrap');

async function stopArangoDBServer() {
  logger.debug('Stopping ArangoDB instance.');
  await stopMockServer();
  logger.debug('ArangoDB instance stopped.');
  app.close();
}

async function startArangoDBServer() {
  try {
    logger.debug('Starting ArangoDB instance.');
    const arangodbPort: number = Number(process.env.ARANGODB_PORT) || 8529;
    const arangodbServer = await startMockServer();
    const arangoDBserverIP = await arangodbServer.getContainerIpAddress();
    const arangoDBserverPort = String(
      await arangodbServer.getMappedPort(arangodbPort),
    );
    baseUrl = `http://${arangoDBserverIP}:${arangoDBserverPort}`;
    process.env.BASE_URL = baseUrl;
    logger.log(`ArangoDB server running at ${baseUrl}`);
    logger.debug('ArangoDB instance started.');
  } catch (error) {
    logger.error(error);
    await stopArangoDBServer();
  }
}

async function bootstrap() {
  await startArangoDBServer();

  app = await NestFactory.create(AppModule);
  await app.listen(port);
  logger.log(
    `arangodb-axiosfit application running at http://localhost:${port}`,
  );
}

bootstrap();

process.on('SIGINT', stopArangoDBServer);
process.on('SIGQUIT', stopArangoDBServer);
process.on('SIGTERM', stopArangoDBServer);
```

After all, you can start the application `npm start` and see the [ArangoDB](https://www.arangodb.com/) server running at your computer:

```shell
odin@asgard:~/issues $ npm start

[Nest] 1193   - 02/28/2020, 5:10:53 PM   [Bootstrap] Starting ArangoDB instance.
[Nest] 1193   - 02/28/2020, 5:11:00 PM   [Bootstrap] ArangoDB server running at http://localhost:42359
[Nest] 1193   - 02/28/2020, 5:11:00 PM   [Bootstrap] ArangoDB instance started.
[Nest] 1193   - 02/28/2020, 5:11:00 PM   [NestFactory] Starting Nest application... +75ms
[Nest] 1193   - 02/28/2020, 5:11:00 PM   [InstanceLoader] AppModule dependencies initialized +9ms
[Nest] 1193   - 02/28/2020, 5:11:00 PM   [RoutesResolver] AppController {/}: +4ms
[Nest] 1193   - 02/28/2020, 5:11:00 PM   [RouterExplorer] Mapped {/, GET} route +2ms
[Nest] 1193   - 02/28/2020, 5:11:00 PM   [NestApplication] Nest application successfully started +2ms
[Nest] 1193   - 02/28/2020, 5:11:00 PM   [Bootstrap] arangodb-axiosfit application running at http://localhost:3000
```

> 'INFO' Check line [Bootstrap] [ArangoDB](https://www.arangodb.com/) server running at [URL] to access [ArangoDB web interface](https://www.arangodb.com/docs/stable/getting-started-web-interface.html).

The base project is done and able to continue creating, now, the [Axiosfit](https://github.com/yggdrasilts/axiosfit) services.

Thankfully to [NestJS module system](https://docs.nestjs.com/modules), we are going to create our own `arangodb` module and we will be able to reuse it in other projects if we want. To do so, we only need to create the module either manually or using the [NestJS CLI](https://docs.nestjs.com/cli/overview) with the following command: `nest g module arangodb`. After that, the project structure will be like this one:

![](https://blog.yggdrasilts.com/content/images/2020/02/image-2.png)

[ArangoDB](https://www.arangodb.com/) has different [REST API](https://www.arangodb.com/docs/stable/http/) endpoints grouped by category. Due to this organization, we are going to create a class, `arangodb.axiosfit.service.ts`, to manage each category and each category will have its own [Axiosfit](https://github.com/yggdrasilts/axiosfit) service into the `services` folder.

![](https://blog.yggdrasilts.com/content/images/2020/02/image-3.png)

> 'INFO' We are only going to create services to manage the endpoints that we need for this project.

To start doing this, we are going to create our [Axiosfit](https://github.com/yggdrasilts/axiosfit) services.

> 'INFO' I am going to show how to create the `collections.axiosfit.service.ts` and you can get the rest of services and entities from the [repository](https://github.com/yggdrasilts/axiosfit/tree/master/samples/NestJS/arangodb-axiosfit/src/arangodb).

Let's start creating the `collections.axiosfit.service.ts` file inside `src/arangodb/services` folder and adding the `CollectionService` class.

```typescript
export class CollectionService {

}
```

Inside this class, we are going to manage some of the [ArangoDB REST API endpoints related to collections](https://www.arangodb.com/docs/stable/http/collection.html).

To enable the [Axiosfit](https://github.com/yggdrasilts/axiosfit) features for this class, we need to add the `@HTTP` main decorator that indicates the class will be an [Axiosfit](https://github.com/yggdrasilts/axiosfit) instance.

```typescript
import { HTTP } from '@yggdrasilts/axiosfit';

@HTTP()
export class CollectionService {

}
```

By default, this decorator enables [Axiosfit](https://github.com/yggdrasilts/axiosfit) instance to use *Observables* but, in this case, we are going to change this default configuration to use *Promises* instead. We are going to enable the [Axiosfit](https://github.com/yggdrasilts/axiosfit) logger as well.

```typescript
import { HTTP } from '@yggdrasilts/axiosfit';

@HTTP({ usePromises: true, enableAxiosLogger: true })
export class CollectionService {

}
```

Ok. We already have the [Axiosfit](https://github.com/yggdrasilts/axiosfit) base service to manage the collection requests. Now, we are going to add some methods needed to [create collections](https://www.arangodb.com/docs/stable/http/collection-creating.html) and [getting their information](https://www.arangodb.com/docs/stable/http/collection-getting.html).

The endpoints to manage will be the following ones:

- Create collection: `POST /_db/{database-name}/_api/collection`
- Get collection information: `GET /_db/{database-name}/_api/collection/{collection-name}`

You can see that we are going to manage some variables in our requests:

- ***Methods***: We need to have a `GET` and `POST`.
- ***Path***: We need to manage the ***database*** and ***collection*** names.
- ***Body***: Also, we need to send ***body*** information.

To manage all of these things, [Axiosfit](https://github.com/yggdrasilts/axiosfit) give us specific [decorators](https://github.com/yggdrasilts/axiosfit#method-decorators):

- ***Methods***: We are going to use `@GET` and `@POST`.
- ***Path***: `@Path` will help us to manage the database and collection names.
- ***Body***: And `@Body` decorator is able to manage the body data.

Finally, [Axiosfit](https://github.com/yggdrasilts/axiosfit) uses [axios](https://github.com/axios/axios) to manage the requests. For this reason, all the requests will respond with an `AxiosResponse` object where the `data` will be the answer to the endpoints.

Getting all together, the `CollectionService` looks like this:

```typescript
import { AxiosResponse, HTTP, GET, POST, Path, Body } from '@yggdrasilts/axiosfit';

import { CollectionCreateOptions, CreateCollectionResponse, CollectionInfo } from '../entities';

@HTTP({ usePromises: true, enableAxiosLogger: true })
export class CollectionService {
  /**
   * Return information about a collection.
   *
   * @param {string} db The name of the database.
   * @param {string} collection The name of the collection.
   * @returns {Promise<AxiosResponse<CollectionInfo>>} Data {@link CollectionInfo}
   * @memberof CollectionService
   */
  @GET('/_db/:db/_api/collection/:collection')
  public getCollectionInfo(@Path('db') db: string, @Path('collection') collection: string): Promise<AxiosResponse<CollectionInfo>> {
    return null;
  }

  /**
   * Create collection.
   *
   * @param {string} db The name of the database.
   * @param {CollectionCreateOptions} body {@link CollectionCreateOptions}
   * @returns {Promise<AxiosResponse<CreateCollectionResponse>>} Data {@link CreateCollectionResponse}
   * @memberof CollectionService
   */
  @POST('/_db/:db/_api/collection')
  public createCollection(@Path('db') db: string, @Body() body: CollectionCreateOptions): Promise<AxiosResponse<CreateCollectionResponse>> {
    return null;
  }
}
```

As you see, using [Axiosfit](https://github.com/yggdrasilts/axiosfit) the requests are organized and stored in one class, you can manage the path using variables and of course, you can send body data if the endpoint requires it.

The last thing that we need to do is to instantiate the [Axiosfit](https://github.com/yggdrasilts/axiosfit) service to be able to use it but before, and after the [Axiosfit](https://github.com/yggdrasilts/axiosfit) services creation, we need to modify our modules, `arangodb.module.ts` and `app.module.ts`, adapt our `main.ts` file and finally, instantiate our [Axiosfit](https://github.com/yggdrasilts/axiosfit) services giving them the [ArangoDB](https://www.arangodb.com/) URL inside our `arangodb.axiosfit.service.ts`.

To do so, we are going to use the [Dynamic Modules](https://docs.nestjs.com/fundamentals/dynamic-modules#dynamic-modules) feature provided by [NestJS](https://nestjs.com/). Using this technique, we will be able to register our modules passing them the [ArangoDB](https://www.arangodb.com/) URL as a variable. Let's start modifying `arangodb.module.ts` file.

```typescript
import { Module, DynamicModule } from '@nestjs/common';

import { ArangoDBService } from './arangodb.axiosfit.service';

@Module({})
export class ArangodbModule {
  static register(baseUrl: string): DynamicModule {
    return {
      module: ArangodbModule,
      providers: [
        {
          provide: 'BASE_URL',
          useValue: baseUrl,
        },
        ArangoDBService,
      ],
      exports: [ArangoDBService],
    };
  }
}
```

You can see that instead of passing the configurations inside the `@Module` decorator, we have created a `static register` method that needs a string variable, `baseUrl`. This will be the [ArangoDB](https://www.arangodb.com/) URL. Also, we have created a new provider `BASE_URL` that we will be used to build the `ArangoDBService`.

```typescript
import { Injectable, Inject, Logger } from '@nestjs/common';

import { Axiosfit } from '@yggdrasilts/axiosfit';

import { AdminService, DatabaseService, CollectionService, BulkService } from './services';

@Injectable()
export class ArangoDBService {
  private readonly logger = new Logger(ArangoDBService.name);

  public adminService: AdminService;
  public databaseService: DatabaseService;
  public collectionService: CollectionService;
  public bulkService: BulkService;

  constructor(@Inject('BASE_URL') baseUrl: string) {
    this.logger.debug(`Initializing ArangoDBService services using ${baseUrl}...`);
    // Initialize ArangoDB services
    this.adminService = new Axiosfit<AdminService>().baseUrl(baseUrl).create(AdminService);
    this.adminService = new Axiosfit<AdminService>().baseUrl(baseUrl).create(AdminService);
    this.databaseService = new Axiosfit<DatabaseService>().baseUrl(baseUrl).create(DatabaseService);
    this.collectionService = new Axiosfit<CollectionService>().baseUrl(baseUrl).create(CollectionService);
    this.bulkService = new Axiosfit<BulkService>().baseUrl(baseUrl).create(BulkService);
  }
}
```

Now, it turns to the `app.module.ts` file.

```typescript
import { Module, DynamicModule } from '@nestjs/common';

import { AppController } from './app.controller';
import { AppService } from './app.service';

import { ArangodbModule } from './arangodb/arangodb.module';

@Module({})
export class AppModule {
  static register(baseUrl: string): DynamicModule {
    return {
      module: AppModule,
      imports: [ArangodbModule.register(baseUrl)],
      controllers: [AppController],
      providers: [AppService],
    };
  }
}
```

We have used the same technique here to pass the `baseUrl` variable between modules. And finally, we need to adapt our `main.ts` where we have the [ArangoDB](https://www.arangodb.com/) URL.

```typescript
...
async function bootstrap() {
  await startArangoDBServer();

  app = await NestFactory.create(AppModule.register(baseUrl));
  await app.listen(port);
  logger.log(
    `arangodb-axiosfit application running at http://localhost:${port}`,
  );
}
...
```

After all these modifications, we can run the application again, `npm start`, and see how the application is running fine.

The last thing to do is use the [Axiosfit](https://github.com/yggdrasilts/axiosfit) services to load the Game Of Thrones data inside [ArangoDB](https://www.arangodb.com/).

> 'INFO' You can download the resources from the [repository](https://github.com/yggdrasilts/axiosfit/tree/master/samples/NestJS/arangodb-axiosfit/test/resources/GameOfThrones) or from the [ArangoDB datasets](https://github.com/arangodb/example-datasets/tree/master/GameOfThrones).

To do so, we are going to add a new method inside our `arangodb.axiosfit.service.ts` called `initGOTData`.

```typescript
import { Injectable, Inject, Logger } from '@nestjs/common';

import { Axiosfit } from '@yggdrasilts/axiosfit';

import * as Characters from '../../test/resources/GameOfThrones/Characters.json';
import * as Traits from '../../test/resources/GameOfThrones/Traits.json';
import * as ChildOf from '../../test/resources/GameOfThrones/ChildOf.json';
import * as Locations from '../../test/resources/GameOfThrones/Locations.json';

import { CollectionType, QueryType } from './entities';

import { AdminService, DatabaseService, CollectionService, BulkService } from './services';

@Injectable()
export class ArangoDBService {
  private readonly logger = new Logger(ArangoDBService.name);

  private readonly db = 'GameOfThrones';
  private readonly collections: { name: string; type: CollectionType; data: object[] }[] = [
    { name: 'Characters', type: CollectionType.DOCUMENT, data: Characters },
    { name: 'Traits', type: CollectionType.DOCUMENT, data: Traits },
    { name: 'ChildOf', type: CollectionType.EDGES, data: ChildOf },
    { name: 'Locations', type: CollectionType.DOCUMENT, data: Locations },
  ];

  public adminService: AdminService;
  public databaseService: DatabaseService;
  public collectionService: CollectionService;
  public bulkService: BulkService;

  constructor(@Inject('BASE_URL') baseUrl: string) {
    this.logger.debug(`Initializing ArangoDBService services using ${baseUrl}...`);
    // Initialize ArangoDB services
    this.adminService = new Axiosfit<AdminService>().baseUrl(baseUrl).create(AdminService);
    this.adminService = new Axiosfit<AdminService>().baseUrl(baseUrl).create(AdminService);
    this.databaseService = new Axiosfit<DatabaseService>().baseUrl(baseUrl).create(DatabaseService);
    this.collectionService = new Axiosfit<CollectionService>().baseUrl(baseUrl).create(CollectionService);
    this.bulkService = new Axiosfit<BulkService>().baseUrl(baseUrl).create(BulkService);
  }

  async initGOTData() {
    const databaseResponse = await this.databaseService.createDatabase({ name: this.db });
    if (databaseResponse.data.error) {
      throw new Error(`Problems creating database '${this.db}'. ${databaseResponse.data.errorMessage}`);
    }
    this.logger.debug(`Database ${this.db} created.`);

    for (const collection of this.collections) {
      const createCollectionResponse = await this.collectionService.createCollection(this.db, {
        name: collection.name,
        type: collection.type,
      });
      if (createCollectionResponse.data.error) {
        throw new Error(`Error creating collection '${collection.name}'.`);
      }
      const collectionInfo = await this.collectionService.getCollectionInfo(this.db, collection.name);
      if (collectionInfo.data.error) {
        throw new Error(`Error checking collection '${collection.name}'.`);
      }
      const bulkImportResponse = await this.bulkService.importJson(
        this.db,
        { type: QueryType.LIST, collection: collection.name },
        collection.data,
      );
      if (bulkImportResponse.data.error) {
        throw new Error(`Error importing data for collection '${collection.name}'.`);
      }
    }
  }
}
```

> 'INFO' This code is based in the [`import.js`](https://github.com/arangodb/example-datasets/blob/master/GameOfThrones/import.js) file from the [ArangoDB datasets](https://github.com/arangodb/example-datasets/tree/master/GameOfThrones).

Finally, we create our `bootstrap` endpoint in our `app.controller.ts` to call this initialization.

```typescript
import { Controller, Get, Logger } from '@nestjs/common';

import { AppService } from './app.service';
import { ArangoDBService } from './arangodb/arangodb.axiosfit.service';

@Controller()
export class AppController {
  private readonly logger = new Logger(AppController.name);

  constructor(private readonly appService: AppService, private readonly arangodbService: ArangoDBService) {}

  @Get('bootstrap')
  async initArangoDB(): Promise<string> {
    this.logger.debug('Initializing ArangoDB with GOT data...');
    await this.arangodbService.initGOTData();
    return 'Game Of Thrones data added to ArangoDB server.';
  }

  @Get()
  async getHello(): Promise<string> {
    this.logger.debug(JSON.stringify((await this.arangodbService.adminService.getStatus()).data));
    return this.appService.getHello();
  }
}
```

Now, we can start our application, `npm start`, go to http://localhost:3000/bootstrap and, if there are no errors, we will see the following message in the browser: `Game Of Thrones data added to ArangoDB server.` and if we go to the terminal, we can see the [Axiosfit](https://github.com/yggdrasilts/axiosfit) logs:

![](https://blog.yggdrasilts.com/content/images/2020/03/image.png)

Also, if we go to ArangoDB web UI, `[ARANGODB_SERVER_URL]/_db/GameOfThrones/_admin/aardvark/index.html#collections`, we will be able to see the collections and edges imported.

> 'INFO' `ARANGODB_SERVER_URL` can be found in the logs in the line `[Bootstarp] ArangoDB server running at...`

![](https://blog.yggdrasilts.com/content/images/2020/03/image-1.png)

## Conclusion

In this post we have seen how using [Axiosfit](https://github.com/yggdrasilts/axiosfit) we have been able to create organized services to call different ArangoDB REST endpoints to load or get data. [Axiosfit](https://github.com/yggdrasilts/axiosfit) give us specific decorators facilitating the request management and storage.

You can see more examples inside the [Axiosfit samples](https://github.com/yggdrasilts/axiosfit/tree/master/samples) and also reading its [documentation](https://github.com/yggdrasilts/axiosfit).

> If you have enjoyed this post and [Axiosfit](https://github.com/yggdrasilts/axiosfit) library, I would really appreciate your feedback giving us Github stars, open Issues requesting new features, problems using the library and also your comments :-)

Enjoy!! 🌳