Nowadays, “web services” often means REST (Representational state transfer) APIs using JSON as the data exchange format.  However, the first generation of web services was built using SOAP (Simple Object Access Protocol), a standard protocol based on XML.  In many enterprises, SOAP web services are still important assets, and some APIs are only available via SOAP.  Unfortunately, SOAP is fairly heavy weight, and working with XML-based SOAP payloads in Node.js is not very fun.  It’s much nicer to use JSON and to wrap or mediate a SOAP service and expose it as a REST API.

As an API server to glue existing and new data sources, LoopBack is designed to facilitate your backend data integration.  With the release of loopback-connector-soap module, you can now easily consume SOAP web services and transform them into REST APIs.

loopback_logo

In this blog, I’ll walk you through the steps to connect to an existing SOAP web service and transform it into a REST API. The example code is available here. It uses a public SOAP-based weather service from here.

Configure a SOAP data source

To invoke a SOAP web service using LoopBack, first configure a data source backed by the SOAP connector.

var ds = loopback.createDataSource('soap', {
        connector: 'loopback-connector-soap'
        remotingEnabled: true,
        wsdl: 'http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL'
    });

SOAP web services are formally described using WSDL (Web Service Description Language) that specifies the operations, input, output and fault messages, and how the messages are mapped to protocols.  So, the most critical information to configure a SOAP data source is a WSDL document.  LoopBack introspects the WSDL document to map service operations into model methods.

Options for the SOAP connector

  • wsdl: HTTP URL or local file system path to the WSDL file, if not present, defaults to < soap web service url >?wsdl.
  • url: URL to the SOAP web service endpoint. If not present, the location attribute of the SOAP address for the service/port from the WSDL document will be used. For example:
  • <wsdl:service name="Weather">
            <wsdl:port name="WeatherSoap" binding="tns:WeatherSoap">
                <soap:address location="http://wsf.cdyne.com/WeatherWS/Weather.asmx" />
            </wsdl:port>
            ...
        </wsdl:service>
    
    
  • remotingEnabled: indicates if the operations will be further exposed as REST APIs
  • operations: maps WSDL binding operations to Node.js methods
  • operations: {
          // The key is the method name
          stockQuote: {
            service: 'StockQuote', // The WSDL service name
            port: 'StockQuoteSoap', // The WSDL port name
            operation: 'GetQuote' // The WSDL operation name
          },
          stockQuote12: {
            service: 'StockQuote', // The WSDL service name
            port: 'StockQuoteSoap12', // The WSDL port name
            operation: 'GetQuote' // The WSDL operation name
          }
        }
    

    Create a model from the SOAP data source

    NOTE: The SOAP connector loads the WSDL document asynchronously. As a result, the data source won’t be ready to create models until it’s connected. The recommended way is to use an event handler for the ‘connected’ event.

    ds.once('connected', function () {
    
            // Create the model
            var WeatherService = ds.createModel('WeatherService', {});
    
            ...
        }
    

    Extend a model to wrap/mediate SOAP operations

    Once the model is defined, it can be wrapped or mediated to define new methods. The following example simplifies the GetCityForecastByZIP operation to a method that takes zip and returns an array of forecasts.

    // Refine the methods
        WeatherService.forecast = function (zip, cb) {
            WeatherService.GetCityForecastByZIP({ZIP: zip || '94555'}, function (err, response) {
                console.log('Forecast: %j', response);
                var result = (!err && response.GetCityForecastByZIPResult.Success) ?
                response.GetCityForecastByZIPResult.ForecastResult.Forecast : [];
                cb(err, result);
            });
        };
    

    The custom method on the model can be exposed as REST APIs. It uses the loopback.remoteMethod to define the mappings.

    // Map to REST/HTTP
        loopback.remoteMethod(
            WeatherService.forecast, {
                accepts: [
                    {arg: 'zip', type: 'string', required: true,
                    http: {source: 'query'}}
                ],
                returns: {arg: 'result', type: 'object', root: true},
                http: {verb: 'get', path: '/forecast'}
            }
        );
    

    Run the example

    git clone git@github.com:strongloop/loopback-connector-soap.git
    cd loopback-connector-soap
    npm install
    node example/weather-rest
    

    Open http://localhost:3000/explorer

    weatherservices

    You can also test the REST API through direct URLs:

  • http://localhost:3000/api/WeatherServices/forecast?zip=94555
  • http://localhost:3000/api/WeatherServices/weather?zip=95131
  • Additional Examples

    There are two additional examples at https://github.com/strongloop/loopback-connector-soap/tree/master/example. Check them out:

  • stock-ws.js: Get stock quotes by symbols
  • node example/stock-ws

  • weather-ws.js: Get weather and forecast information for a given zip code
  • node example/weather-ws

    What’s next?