0.0.1 • Published 2 years ago

@andcup/retrofit v0.0.1

Weekly downloads
1
License
MIT
Repository
github
Last release
2 years ago

Retrofit

GitHub package.json version NPM GitHub issues GitHub pull requests

Commitizen friendly npm

Twitter Follow

Retrofit is a project inspired by Retrofit to create declarative HTTP clients using axios as the http client for browsers and nodejs, all the TypeScript features and RxJS to manage the requests using Reactive programming and all the Observable powers (also, you can use Promises as well).

Table of Contents generated with DocToc

Purpose

This project has been thought to be used inside TypeScript projects to use its incredible features to build frontend and/or backend applications.

Using Retrofit inside your project, you will be able to organize all your requests as a typical TypeScript class. In that way, all of your application requests can be stored in a unique object and be managed. See our Examples section or our application samples to know more about how to use it.

IMPORTANT: Retrofit only works for building frontend applications using Angular or backend applications using nodejs with TypeScript. Althought Retrofit can be used in a typical TypeScript application, we strongly recommend to use NestJS framework to build backend applications.

Install

npm i --save @yggdrasilts/Retrofit

Contributing

If you want to contribute this amazing project, see CONTRIBUTING Guide.

Documentation

This project use compodoc to generate the full documentation. To see it, executes npm run compodoc.

Available Decorators

As we all know, to build a request is necessary a URL and, using one of the top TypeScript feature, the TypeScript Decorators, Retrofit give you some specifical Decorators to create an awesome service to store all the information to form your URL to manage your HTTP requests.

Class Decorators

@HTTP is the main Decorator to configure your Retrofit service and indicates that the class is an Retrofit instance. Also, it can be configured with the base path of your API server using its endpointPath property and configure more Retrofit option using the RetrofitConfig object.

@Interceptors decorator can be use to add interceptors to the requests or responses. These interceptors must implements RetrofitInterceptor interface and you can add as many as you want separated by comma. You can see the Interceptors section for more information.

Considerations

Angular applications: Due to the uglify process when you build an Angular application with --prod parameters, you need to create a static readonly property called serviceName inside the class to give it a unique name that works as unique identifier used by Retrofit. In the Usage section you can see how to use it. You also have more samples inside the samples folder.

Method Decorators

To request any API server, it is needed to know with method is necessary. For this reason Retrofit give you Decorators to indicate which method has to be used.

Parameter Decorators

Another part is the path and the body of your request and Retrofit also has these Decorators to configure your service.


Examples of using all of these Decorators are shown in the Examples section or you can find specific samples inside the samples folder.


Configuration

  • RetrofitConfig
    • usePromises: Default false. Configure Retrofit service to return Observable object or a Promise.
    • enableAxiosLogger: Default false. Configure Retrofit service to use the Retrofit logger to print the request and response to the console.

Usage

To build an Retrofit service a few steps need to be done:

1. Create a service class

A service class needs to be created because it will store all the requests information. The following code shows a simple service:

@HTTP('/simple')
export class SimpleService {
  @GET('/service')
  public getSimpleService(): Observable<AxiosResponse<string>> {
    return null;
  }
}

If you want to use Promises instead of Observable:

@HTTP('/simple', { usePromises: true })
export class SimpleService {
  @GET('/service')
  public getSimpleService(): Promise<AxiosResponse<string>> {
    return null;
  }
}

1.1 Service class for Angular application uglifying the code to deploy in production

@HTTP('/simple')
export class SimpleService {
  private static readonly serviceName = 'SimpleService';

  @GET('/service')
  public getSimpleService(): Observable<AxiosResponse<string>> {
    return null;
  }
}

2. Create Retrofit instance

Once the service class is created, the Retrofit instance can be built:

const simpleService = new Retrofit<SimpleService>().baseUrl('http://simpleservice.com').create(SimpleService);

3. Perform an Retrofit request

After all steps, the service can be managed as a typical class:

simpleService.getSimpleService().subscribe(
  axiosResponse => console.log(axiosResponse.data),
  axiosError => console.error(axiosError),
);

4. Using Promises

Retrofit manage responses using Observables or Promises. The above example shows how to use Observables but, if you prefer use Promises, a minimal change has to be done:

4.1 Modify service class return type

@HTTP('/simple')
export class SimpleService {
  @GET('/service')
  public getSimpleService(): Promise<AxiosResponse<string>> {
    return null;
  }
}

Once the change is made, you can use Retrofit response like a typical Promise:

simpleService
  .getSimpleService()
  .then(axiosResponse => console.log(axiosResponse.data))
  .catch(axiosError => console.error(axiosError));

Headers

HTTP headers let the client and the server pass additional information with an HTTP request or response. Retrofit has various decorators that enabled you to modify these HTTP headers.

  • The @Header parameter decorator gives you the option to add some header individually.
@GET(TestRoutes.GET.REQUEST.URL)
public performGetRequestWithHeader(@Header('Authorization') token: string): Observable<AxiosResponse<string>> {
  return null;
}
  • The @HeaderMap parameter decorator gives you the option to pass a map with various headers.
@GET(TestRoutes.GET.REQUEST.URL)
public performGetRequestWithHeaderMap(@HeaderMap('map') map: IHeadersMap): Observable<AxiosResponse<string>> {
  return null;
}

Other features

Interceptors

Interceptors are other axios feature that Retrofit implements.

There are 2 ways to use interceptors:

  1. Using the @Interceptors(...interceptors: RetrofitInterceptor[]) decorator:

With this first way, the interceptor, or a list of interceptors separated by commas, needs to be added as a decorator parameter:

@HTTP('/simple')
@Interceptors(SimpleInterceptor)
export class SimpleService {
  @GET('/service')
  public getSimpleService(): Promise<AxiosResponse<string>> {
    return null;
  }
}

In the following sample you can see how to implement it and alse see the differences.

export class SimpleInterceptor implements RetrofitRequestInterceptor, RetrofitResponseInterceptor {
  onRequest(config: AxiosRequestConfig): AxiosRequestConfig | Promise<AxiosRequestConfig> {
    // tslint:disable-next-line: no-string-literal
    config.headers['authorization'] = 'Bearer token';
    return config;
  }

  onResponse(response: AxiosResponse<any>): AxiosResponse<any> | Promise<AxiosResponse<any>> {
    // tslint:disable-next-line: no-string-literal
    const currentData = response.data;
    response.data = {
      ...currentData,
      newData: 'new',
    };
    return response;
  }
}
  1. Add the interceptor when the Retrofit service is created.

Using this second method, a request and/or response interceptors must be created when the Retrofit instance is created.

export const simpleService = new Retrofit<SimpleService>()
  .baseUrl('http://simpleservice.com')
  .addInterceptor(interceptor)
  .create(SimpleService);
Request Interceptor
export class SimpleNewInterceptorRequest implements RetrofitRequestInterceptor {
  onRequest(config: AxiosRequestConfig): AxiosRequestConfig | Promise<AxiosRequestConfig> {
    // tslint:disable-next-line: no-string-literal
    config.headers['authorization'] = 'Bearer token';
    return config;
  }
}
Response Interceptor
export class SimpleInterceptorResponse implements RetrofitResponseInterceptor {
  onResponse(response: AxiosResponse<any>): AxiosResponse<any> | Promise<AxiosResponse<any>> {
    // tslint:disable-next-line: no-string-literal
    const currentData = response.data;
    response.data = {
      ...currentData,
      newData: 'new',
    };
    return response;
  }
}

Examples

  • Create class with the endpoints methods:

    • Using Observables:
import {
  HTTP,
  Header,
  HeaderMap,
  GET,
  DELETE,
  HEAD,
  POST,
  PUT,
  PATCH,
  Path,
  Body,
  Observable,
  AxiosResponse,
  Param,
  ParamMap,
  IParamMap,
  IHeadersMap,
} from '../../../src';
import { TestRoutes } from '../TestRoutes';

@HTTP(TestRoutes.BASE, { enableAxiosLogger: true })
export class TestObservableService {
  private static readonly serviceName = 'TestObservableService';

  @GET(TestRoutes.GET.REQUEST.URL)
  public performGetRequest(): Observable<AxiosResponse<string>> {
    return null;
  }

  @GET(TestRoutes.GET.REQUEST.URL)
  public performGetRequestWithHeader(@Header('Authorization') token: string): Observable<AxiosResponse<string>> {
    return null;
  }

  @GET(TestRoutes.GET.REQUEST.URL)
  public performGetRequestWithHeaderMap(@HeaderMap('map') map: IHeadersMap): Observable<AxiosResponse<string>> {
    return null;
  }

  @GET(TestRoutes.GET.REQUEST.URL)
  public performGetRequestWithParameter(@Param('id') id: string): Observable<AxiosResponse<string>> {
    return null;
  }

  @GET(TestRoutes.GET.REQUEST.URL)
  public performGetRequestWithParametersMap(@ParamMap('map') map: IParamMap): Observable<AxiosResponse<string>> {
    return null;
  }

  @GET(TestRoutes.GET.REQUEST.URL)
  public performGetRequestWithParameters(
    @Param('id1') id1: string,
    @Param('id2') id2: string,
    @ParamMap('map') map: IParamMap,
  ): Observable<AxiosResponse<string>> {
    return null;
  }

  @GET(TestRoutes.GET.WITH_REQUEST_INTERCEPTOR.URL)
  public performGetRequestAddingReqInterceptor(): Observable<AxiosResponse<string>> {
    return null;
  }

  @GET(TestRoutes.GET.WITH_RESPONSE_INTERCEPTOR.URL)
  public performGetRequestAddingResInterceptor(): Observable<AxiosResponse<string>> {
    return null;
  }

  @GET(TestRoutes.GET.WITH_PARAM.URL)
  public performGetRequestUsingAPathVariable(@Path(TestRoutes.GET.WITH_PARAM.PARAMS.ID) id: string): Observable<AxiosResponse<string>> {
    return null;
  }

  @GET(TestRoutes.GET.WITH_PARAMS.URL)
  public performGetRequestUsingPathVariables(
    @Path(TestRoutes.GET.WITH_PARAMS.PARAMS.ID) id: string,
    @Path(TestRoutes.GET.WITH_PARAMS.PARAMS.ID2) id2: string,
  ): Observable<AxiosResponse<string>> {
    return null;
  }

  @DELETE(TestRoutes.DELETE.REQUEST.URL)
  public performDeleteRequest(): Observable<AxiosResponse<string>> {
    return null;
  }

  @HEAD(TestRoutes.HEAD.REQUEST.URL)
  public performHeadRequest(): Observable<AxiosResponse<string>> {
    return null;
  }

  @POST(TestRoutes.POST.REQUEST.URL)
  public performPostRequest(@Body() body: any): Observable<AxiosResponse<any>> {
    return null;
  }

  @PUT(TestRoutes.PUT.REQUEST.URL)
  public performPutRequest(@Body() body: any): Observable<AxiosResponse<any>> {
    return null;
  }

  @PATCH(TestRoutes.PATCH.REQUEST.URL)
  public performPatchRequest(@Body() body: any): Observable<AxiosResponse<any>> {
    return null;
  }
}
  • Using Promises:
import { HTTP, GET, DELETE, HEAD, POST, PUT, PATCH, Path, Body, AxiosResponse } from '../../src';
import { TestRoutes } from './TestRoutes';

@HTTP(TestRoutes.BASE, { usePromises: true })
export class TestService {
  @GET(TestRoutes.GET.REQUEST.URL)
  public performGetRequest(): Promise<AxiosResponse<string>> {
    return null;
  }

  @GET(TestRoutes.GET.WITH_REQUEST_INTERCEPTOR.URL)
  public performGetRequestAddingReqInterceptor(): Promise<AxiosResponse<string>> {
    return null;
  }

  @GET(TestRoutes.GET.WITH_RESPONSE_INTERCEPTOR.URL)
  public performGetRequestAddingResInterceptor(): Promise<AxiosResponse<string>> {
    return null;
  }

  @GET(TestRoutes.GET.WITH_PARAM.URL)
  public performGetRequestUsingAPathVariable(@Path(TestRoutes.GET.WITH_PARAM.PARAMS.ID) id: string): Promise<AxiosResponse<string>> {
    return null;
  }

  @GET(TestRoutes.GET.WITH_PARAMS.URL)
  public performGetRequestUsingPathVariables(
    @Path(TestRoutes.GET.WITH_PARAMS.PARAMS.ID) id: string,
    @Path(TestRoutes.GET.WITH_PARAMS.PARAMS.ID2) id2: string,
  ): Promise<AxiosResponse<string>> {
    return null;
  }

  @DELETE(TestRoutes.DELETE.REQUEST.URL)
  public performDeleteRequest(): Promise<AxiosResponse<string>> {
    return null;
  }

  @HEAD(TestRoutes.HEAD.REQUEST.URL)
  public performHeadRequest(): Promise<AxiosResponse<string>> {
    return null;
  }

  @POST(TestRoutes.POST.REQUEST.URL)
  public performPostRequest(@Body() body: any): Promise<AxiosResponse<any>> {
    return null;
  }

  @PUT(TestRoutes.PUT.REQUEST.URL)
  public performPutRequest(@Body() body: any): Promise<AxiosResponse<any>> {
    return null;
  }

  @PATCH(TestRoutes.PATCH.REQUEST.URL)
  public performPatchRequest(@Body() body: any): Promise<AxiosResponse<any>> {
    return null;
  }
}
  • Create the Retrofit instance:
import { Retrofit } from '@yggdrasilts/Retrofit';

const methodsService = new Retrofit<TestService>().baseUrl(process.env.MOCK_SERVER_URL).create(TestService);
  • Call methods using observables:

    • Using Observables:
import { AxiosResponse, AxiosError } from '@yggdrasilts/Retrofit';

methodsService.performGetRequest().subscribe(
  (response: AxiosResponse<string>) => console.log('OK', response.data),
  (error: AxiosError<any>) => console.error('KO', error),
);
  • Using Promises:
import { AxiosResponse, AxiosError } from '@yggdrasilts/Retrofit';

methodsService
  .performGetRequest()
  .then((response: AxiosResponse<string>) => console.log('OK', response.data))
  .catch((error: AxiosError<any>) => console.error('KO', error));

NOTE: The example code can be seen in the test folder.