---
# System prepended metadata

title: Configuraited http service
tags: [Typescript]

---

# Configuraited http service

###### tags: `Typescript`

> Пример конфигурироваемого Http service для фронтенд проекта.

Сначала надо определить конфиг для апишки

```typescript=
interface IApiUrls {
  baseApiUrl: string;
  [key: string]: string;
}

interface IConfig {
  [key: string]: {
    host: string;
    [key: string]: string;
  };
}

export interface IApiConfig {
  api: IConfig;
}

function getConfig(urls: IApiUrls, cachingLifeTime?: number): IApiConfig {
  return {
    api: Object.assign({
      caching: {
        lifetime: cachingLifeTime !== undefined ? cachingLifeTime : 5 * 1000, // 5 sec
      },
    }, getApiConfig(urls)),
  };
}

function getApiConfig({ baseApiUrl }: IApiUrls): IConfig {
  return {
    signing: {
      host: `${baseApiUrl}/signing/v2/`,
      statusByAccessToken: 'sessions/status/:accessToken',
      updateSubjectInfo: 'sessions/:sessionId/tasks/updateSubjectInfo',
      sessionTask: 'sessions/:sessionId/tasks/:taskId',
      uploadSignature: 'signature',
      signingTaskExit: 'sessions/status/:accessToken/exit',
    },
  };
}

export default {
  getConfig,
};
```

Потом определяем файл с конфигом для всего проекта и импортим туда конфиг апишки

```typescript=
import _merge from 'lodash/merge';
import defaultConfig from './default';
import defaultApi from './default-api';

module.exports = _merge(
  {},
  defaultConfig,
  defaultApi.getConfig({ baseApiUrl: defaultConfig.baseApiUrl }),
  {
    isSsr: true,
  },
);
```

Далее делаем api service который будет работать с этим конифгом. Вам понадобиться  HttpService это [классический сервис описанный в этой статейке](https://hackmd.io/@devhouse/classic-http-service)
```typescript=
import { HttpService } from './http.service';
import { AxiosError, AxiosRequestConfig, AxiosResponse, Method } from 'axios';
import * as Sentry from '@sentry/browser';

import _forOwn from 'lodash/forOwn';
import _merge from 'lodash/merge';
import _get from 'lodash/get';
import _isUndefined from 'lodash/isUndefined';

import { ENVS } from '../constants/common';

class ApiServiceClass {
  private readonly _http = HttpService;

  public defaultOptions: AxiosRequestConfig;

  constructor(private _appConfig: any) {
    if (!this._appConfig) {
      throw new Error('Api config is not defined');
    } else {
      this.defaultOptions = this._appConfig.api.options;
    }
  }

  public get<T = Object>(apiKey: string, pathKey: string, urlParams?: {}, options?: AxiosRequestConfig): Promise<T> {
    const url = this.buildUrl(apiKey, pathKey, urlParams);

    if (!url) {
      throw new Error(`url is not found please check keys, ${apiKey}, ${pathKey}`);
    }
    return this.handleResponse<T>(this._http.get<T>(url, this.getOptions(options)));
  }

  public post<T = Object>(apiKey: string, pathKey: string, urlParams?: {}, body?: {}, options?: AxiosRequestConfig): Promise<T> {
    return this._payloadRequest.apply(this, ['post', apiKey, pathKey, urlParams, body, options]) as Promise<T>;
  }

  public patch<T = Object>(apiKey: string, pathKey: string, urlParams?: {}, body?: {}, options?: AxiosRequestConfig): Promise<T> {
    return this._payloadRequest.apply(this, ['patch', apiKey, pathKey, urlParams, body, options]) as Promise<T>;
  }

  public put<T = Object>(apiKey: string, pathKey: string, urlParams?: {}, body?: {}, options?: AxiosRequestConfig): Promise<T> {
    return this._payloadRequest.apply(this, ['put', apiKey, pathKey, urlParams, body, options]) as Promise<T>;
  }

  public remove<T = Object>(apiKey: string, pathKey: string, urlParams?: {}, options?: AxiosRequestConfig): Promise<T> {
    const url = this.buildUrl(apiKey, pathKey, urlParams);

    if (!url) {
      throw new Error(`url is not found please check keys, ${apiKey}, ${pathKey}`);
    }

    return this.handleResponse(this._http.delete(url, this.getOptions(options)));
  }

  public getOptions(options: AxiosRequestConfig = {}): AxiosRequestConfig {
    let requestOptions = this.defaultOptions;
    if (options) {
      requestOptions = _merge({}, this.defaultOptions, options);
    }

    return requestOptions;
  }

  public handleResponse<T>(promise: Promise<AxiosResponse<T>>): Promise<T> {
    return promise
      .then((res: AxiosResponse<T>) => {
        return res.data;
      })
      .catch((err: AxiosError) => {
        Sentry.captureException(err);
        throw err;
      });
  }

  public buildUrl(apiKey: string, pathKey: string, urlParams: {} = {}): string {
    if (apiKey.indexOf('api.') !== 0) {
      apiKey = 'api.' + apiKey;
    }

    const api = _get(this._appConfig, apiKey);
    const path = _get(api, pathKey);

    if (_isUndefined(api) || _isUndefined(path)) {
      console
        .error(`apiKey: '${apiKey}' or pathKey '${pathKey}' were not found in config, ${JSON.stringify(this._appConfig)}`);
      Sentry
        .captureException(`apiKey: '${apiKey}' or pathKey '${pathKey}' were not found in config, ${JSON.stringify(this._appConfig)}`);
      return '';
    }

    return api.host + this._fillUrl(path, urlParams);
  }

  private _payloadRequest<T>(method: Method, apiKey: string,
                             pathKey: string, urlParams?: {}, body?: any, options?: AxiosRequestConfig): Promise<T> {
    const url = this.buildUrl(apiKey, pathKey, urlParams);
    if (!url) {
      throw new Error(`url is not found please check keys, ${apiKey}, ${pathKey}`);
    }

    return this.handleResponse(
      this._http.request(
        this.getOptions({
          method,
          url,
          data: body,
          ...options,
        }),
      ),
    );
  }

  private _fillUrl(url: string, params: {}): string {
    _forOwn(params, (value, key) => {
      url = url.replace(new RegExp(':' + key), value);
    });

    return url;
  }
}

/***
 * TODO
 * Improve get configs by env
 */

let environment_path = 'environment';

if (process.env.ENV === ENVS.DEV) {
  environment_path += '.dev';
}

if (process.env.ENV === ENVS.STAGE) {
  environment_path += '.stage';
}

if (process.env.ENV === ENVS.PROD) {
  environment_path += '.prod';
}

const environment = require(`../../environments/${environment_path}`);

export const getSigningApiAuthToken = (): string => {
  return process.env.SIGNING_API_AUTH_TOKEN || 'XXXXX';
};

export const ApiService = new ApiServiceClass(environment);
```

И потом это можно вот так использовать

```typescript=
import { ApiService, getSigningApiAuthToken } from '../services/api.service';
import { ISession } from './interfaces/i-session';
import { IUpdateSignerInfoPayload } from './interfaces/i-update-signer-info-payload';
import { IUploadSignaturePayload } from './interfaces/i-upload-signature-payload';

export class SigningApiService {
  public static getSessionByAccessToken(accessToken: string, remoteIpAddress?: string): Promise<ISession> {
    return ApiService.get('signing', 'statusByAccessToken', { accessToken }, {
      headers: {
        Authorization: 'Bearer ' + getSigningApiAuthToken(),
        'X-Forwarded-For': remoteIpAddress
      }
    });
  }
  public static updateSignerInfo(sessionId: string, payload: IUpdateSignerInfoPayload): Promise<ISession> {
    return ApiService.put('signing', 'updateSubjectInfo', { sessionId }, payload, {
      headers: {
        Authorization: 'Bearer ' + getSigningApiAuthToken(),
      }
    });
  }
  public static uploadSignature(payload: IUploadSignaturePayload): Promise<{redirectUrl: string}> {
    const formData = new FormData();

    formData.append('signatureFile', payload.signatureFile, payload.signatureFile.name);
    formData.append('signatureInput', JSON.stringify({
      accessToken: payload.accessToken,
      signerName: payload.signerName,
      personalNumber: payload.personalNumber,
    }));

    return ApiService.post('signing', 'uploadSignature', {}, formData, {
      headers: {
        'Content-Type': 'multipart/form-data',
        Accept: 'application/json',
        Authorization: 'Bearer ' + getSigningApiAuthToken(),
      }
    });
  }
  public static taskExit(accessToken: string): Promise<{ redirectUrl: string }> {
    return ApiService.post('signing', 'signingTaskExit', { accessToken }, {}, {
      headers: {
        Authorization: 'Bearer ' + getSigningApiAuthToken(),
      }
    });
  }
}

```