Your API implementation often needs to interact with REST APIs, SOAP Web Services, gRPC microservices, or other forms of APIs.
To facilitate calling other APIs or web services, we introduce
@loopback/service-proxy
module to provide a common set of interfaces for
interacting with backend services.
Installation
$ npm install --save @loopback/service-proxy
Usage
Define a data source for the service backend
import {juggler} from '@loopback/service-proxy';
const ds: juggler.DataSource = new juggler.DataSource({
name: 'GoogleMapGeoCode',
connector: 'rest',
options: {
headers: {
accept: 'application/json',
'content-type': 'application/json',
},
},
operations: [
{
template: {
method: 'GET',
url: 'http://maps.googleapis.com/maps/api/geocode/{format=json}',
query: {
address: '{street},{city},{zipcode}',
sensor: '{sensor=false}',
},
responsePath: '$.results[0].geometry.location[0]',
},
functions: {
geocode: ['street', 'city', 'zipcode'],
},
},
],
});
Install the REST connector used by the new datasource:
$ npm install --save loopback-connector-rest
Declare the service interface
To promote type safety, we recommend you to declare data types and service interfaces in TypeScript and use them to access the service proxy.
interface GeoCode {
lat: number;
lng: number;
}
interface GeoService {
geocode(street: string, city: string, zipcode: string): Promise<GeoCode>;
}
Alternately, we also provide a weakly-typed generic service interface as follows:
/**
* A generic service interface with any number of methods that return a promise
*/
export interface GenericService {
[methodName: string]: (...args: any[]) => Promise<any>;
}
To reference the GenericService
:
import {GenericService} from '@loopback/service-proxy';
NOTE: We’ll introduce tools in the future to generate TypeScript service interfaces from service specifications such as OpenAPI spec.
Declare service proxies for your controller
If your controller needs to interact with backend services, declare such
dependencies using @serviceProxy
on constructor parameters or instance
properties of the controller class.
import {serviceProxy} from '@loopback/service-proxy';
export class MyController {
@serviceProxy('geoService')
private geoService: GeoService;
}
Get an instance of your controller
context.bind('controllers.MyController').toClass(MyController);
const myController = await context.get<MyController>(
'controllers.MyController',
);
Make service proxies easier to test
While @serviceProxy
decorator makes it easy to use service proxies in
controllers, it makes it difficult to write integration tests to verify that
service proxies are correctly configured/implemented in respect to the most
recent API provided by the backend service implementation. To make service
proxies easy to test, we recommend to write a
Provider class that will allow both
controllers and integration tests to access the same service proxy
implementation.
import {getService, juggler} from '@loopback/service-proxy';
import {inject, Provider} from '@loopback/core';
import {GeocoderDataSource} from '../datasources/geocoder.datasource';
export class GeoServiceProvider implements Provider<GeoService> {
constructor(
@inject('datasources.geoService')
protected datasource: juggler.DataSource = new GeocoderDataSource(),
) {}
value(): Promise<GeocoderService> {
return getService(this.datasource);
}
}
In your application, apply
ServiceMixin
and use app.serviceProvider
API to create binding for the geo service proxy.
app.serviceProvider(GeoServiceProvider);
Finally, modify the controller to receive our new service proxy in the constructor:
export class MyController {
@inject('services.GeoService')
private geoService: GeoService;
}
Please refer to Testing Your Application for guidelines on integration testing.