LoopBack 4 Todo Application Tutorial - Integrate with a geo-coding service

Services

To call other APIs and web services from LoopBack applications, we recommend touse Service Proxies as a design pattern for encapsulating low-levelimplementation details of communication with 3rd-party services and providingJavaScript/TypeScript API that’s easy to consume e.g. from Controllers. SeeCalling other APIs and web servicesfor more details.

In LoopBack, each service proxy is backed by aDataSource, this datasource leverages one ofthe service connectors to make outgoing requests and parse responses returned bythe service.

In our tutorial, we will leverageUS Census Geocoder API to converttextual US addresses into GPS coordinates, thus enabling client applications ofour Todo API to display location-based reminders,

Tip: In a real project, you may want to use a geocoding service that covers morecountries beyond USA and provides faster responses than US Census Geocoder API,for example IBM’s Weather Company Dataor Google Maps Platform.

Configure the backing datasource

Run lb4 datasource to define a new datasource connecting to Geocoder RESTservice. When prompted for a connector to use, select “REST services”.

  1. $ lb4 datasource
  2. ? Datasource name: geocoder
  3. ? Select the connector for geocoder: REST services (supported by StrongLoop)
  4. ? Base URL for the REST service:
  5. ? Default options for the request:
  6. ? An array of operation templates:
  7. ? Use default CRUD mapping: No
  8. create src/datasources/geocoder.datasource.json
  9. create src/datasources/geocoder.datasource.ts
  10. # npm will install dependencies now
  11. update src/datasources/index.ts
  12. Datasource Geocoder was created in src/datasources/

Edit the newly created datasource configuration to configure Geocoder APIendpoints. Configuration options provided by REST Connector are described in ourdocs here: REST connector.

/src/datasources/geocoder.datasource.json

  1. {
  2. "connector": "rest",
  3. "options": {
  4. "headers": {
  5. "accept": "application/json",
  6. "content-type": "application/json"
  7. }
  8. },
  9. "operations": [
  10. {
  11. "template": {
  12. "method": "GET",
  13. "url": "https://geocoding.geo.census.gov/geocoder/locations/onelineaddress",
  14. "query": {
  15. "format": "{format=json}",
  16. "benchmark": "Public_AR_Current",
  17. "address": "{address}"
  18. },
  19. "responsePath": "$.result.addressMatches[*].coordinates"
  20. },
  21. "functions": {
  22. "geocode": ["address"]
  23. }
  24. }
  25. ]
  26. }

Implement a service provider

Create a new directory src/services and add the following two new files:

  • src/services/geocoder.service.ts defining TypeScript interfaces for Geocoderservice and implementing a service proxy provider.
  • src/services/index.ts providing a conventient access to all services via asingle import statement.

src/services/geocoder.service.ts

  1. import {getService, juggler} from '@loopback/service-proxy';
  2. import {inject, Provider} from '@loopback/core';
  3. import {GeocoderDataSource} from '../datasources/geocoder.datasource';
  4. export interface GeoPoint {
  5. /**
  6. * latitude
  7. */
  8. y: number;
  9. /**
  10. * longitude
  11. */
  12. x: number;
  13. }
  14. export interface GeocoderService {
  15. geocode(address: string): Promise<GeoPoint[]>;
  16. }
  17. export class GeocoderServiceProvider implements Provider<GeocoderService> {
  18. constructor(
  19. @inject('datasources.geocoder')
  20. protected dataSource: juggler.DataSource = new GeocoderDataSource(),
  21. ) {}
  22. value(): Promise<GeocoderService> {
  23. return getService(this.dataSource);
  24. }
  25. }

src/services/index.ts

  1. export * from './geocoder.service';

Enhance Todo model with location data

Add two new properties to our Todo model: remindAtAddress and remindAtGeo.

src/models/todo.model.ts

  1. @model()
  2. export class Todo extends Entity {
  3. // original code remains unchanged, add the following two properties:
  4. @property({
  5. type: 'string',
  6. })
  7. remindAtAddress?: string; // address,city,zipcode
  8. @property({
  9. type: 'string',
  10. })
  11. remindAtGeo?: string; // latitude,longitude
  12. }

Look up address location in the controller

Finally, modify TodoController to look up the address and convert it to GPScoordinates when a new Todo item is created.

Import GeocodeService interface into the TodoController and then modify theController constructor to receive GeocodeService as a new dependency.

src/controllers/todo.controller.ts

  1. import {inject} from '@loopback/core';
  2. import {GeocoderService} from '../services';
  3. export class TodoController {
  4. constructor(
  5. @repository(TodoRepository) protected todoRepo: TodoRepository,
  6. @inject('services.GeocoderService') protected geoService: GeocoderService,
  7. ) {}
  8. // etc.
  9. }

Modify createTodo method to look up the address provided in remindAtAddressproperty and convert it to GPS coordinates stored in remindAtGeo.

src/controllers/todo.controller.ts

  1. export class TodoController {
  2. // constructor, etc.
  3. @post('/todos')
  4. async createTodo(@requestBody() todo: Todo) {
  5. if (!todo.title) {
  6. throw new HttpErrors.BadRequest('title is required');
  7. }
  8. if (todo.remindAtAddress) {
  9. // TODO handle "address not found"
  10. const geo = await this.geoService.geocode(todo.remindAtAddress);
  11. // Encode the coordinates as "lat,lng"
  12. todo.remindAtGeo = `${geo[0].y},${geo[0].x}`;
  13. }
  14. return this.todoRepo.create(todo);
  15. }
  16. // other endpoints remain unchanged
  17. }

Congratulations! Now your Todo API makes it easy to enter an address for areminder and have the client application show the reminder when the devicereaches close proximity of that address based on GPS location.

Navigation

Previous step: Putting it all together