With Node.js: Wrap Backend SOAP Webservices In a RESTful API

Paul Robu
METRO SYSTEMS Romania
4 min readMar 3, 2020

Microservices exposed as REST APIs are very popular in the software industry, as a new approach to distributed systems. Most of the principles of Service Oriented Architecture (SOA) apply to microservices architecture as well. But, since they are modeled (deployed and scaled) in an isolated fashion, microservices avoid the problems of traditional middleware systems.

As seen in a previous article, legacy systems are still important assets in many enterprises. If some unique dataset or complex functionality is available only as a SOAP webservice, modern clients can still consume it if we wrap it with a RESTful interface, in interim until a true cloud native solution is ready.

Quick SOAP and REST Overview

SOAP (Simple Object Access Protocol) is a protocol, whereas REST (Representational State Transfer) is a guideline for architectural style. Both are answers to the same question: “how to access Web Services?”

The analogy of a postcard and an envelope is used frequently: REST is lightweight and consumes less bandwidth (paper), like a postcard. SOAP is like an envelope: there is extra work required on both ends to package and unpack the message. But unlike a postcard, the content of REST requests and responses is secure. REST uses the security of the underlying transport mechanism, usually HTTPS. On the other hand, SOAP implements its own security built-in standards, known as WS-Security.

A SOAP message is encoded as an XML document, consisting of an <Envelope> element, which contains an optional <Header> element, and a mandatory <Body> element. REST permits many different data formats including plain text, HTML, XML, and JSON.

In SOAP, the server objects are accessed remotely, while REST is representing them on the client side (the “Representational” term in REST). The design is such that each component in a RESTful API is a resource that can be accessed using standard HTTP methods (e.g., GET — reads a resource, PUT — creates a new resource, POST — updates an existing resource, DELETE — removes a resource).

System Architecture

In this example, the business need is to enroll users into a Loyalty program. Our RESTful API receives the JSON input, generates the SOAP request for the backend Loyalty WebService and converts the XML response back into JSON format.

Architecture of a RESTful wrapper around a SOAP Web Service
Architecture for a REST wrapper over a SOAP Web Service

Let’s Get Coding

SOAP webservices are formally described using a WSDL (Web Service Description Language) document, that specifies the operations, input, output and fault messages. For our use-case:

WSDL for the SOAP webservice

From WSDL, we can determine what are the SOAP request message elements for input (which will be used in our _updateMemberClassXml method below) and what output to expect.

For simplicity, I will skip the Node.js basics for creating an Express server, setting up routes, authentication and input validation. The REST API is implemented using https://www.npmjs.com/package/soap, so let’s add node-soap module to the list of dependencies:

npm install soap

Then, create a client that will consume the SOAP webservice. Below is the simplified version:

const soap = require('soap')
const url = 'https://{soap-server}:{port}/LoyaltyMemberService?wsdl'
// const ErrorCodes
class LoyaltyRestService {
async enrollMember(memberId, loyProgramName) {
let me = this
let requestInput = {
loyProgramName: loyProgramName,
memberId: CustomerNumber,
}
var argsxml = this._updateMemberClassXml(requestInput)
return new Promise((resolve, reject) => {
soap.createClient(url, {wsdl_headers: {Authorization: auth}}, function(err, client) {
if (err) {
me._logger.error(`Cannot connect to remote soap url: ${url}`, err)
return reject(ErrorCodes['5005'])
}

The Promise object from above will have the completion (or eventual failure) and the resulting value of the asynchronous operation.
Then we call the method defined by the SOAP webservice:

    client.updateAllMember_V01(argsxml, function(methodCallError, result) {
try {
programMember = me._updateMemberClassResult(result)
} catch (err) {
me._logger.error(`Failed to enroll $${memberId} for ${loyProgramName} with result ${JSON.stringify(result)}`, err)
return reject(ErrorCodes['5006'])
}
me._logger.info(`Member $${memberId} successfully enrolled for ${loyProgramName}`)
return resolve(programMember)
})
})
})
}

The response from backend is formatted as JSON:

_updateMemberClassResult(result) {
let origin = result.ListOfLoymember...MemberV01extOut.UpdateMemberOut
let response = {
'customerId': origin['CustomerId'],
}
return response
}

The client communicates with backend over HTTP (POST), with the request payload represented in XML:

 _updateMemberClassXml(requestInput) {
return {
_xml: '<cus:updateAllMember_V01_Input xmlns:cus="http://siebel.com/CustomUI" xmlns:mg="http://www.siebel.com/xml/MG_loyMemberService_updateAllMember_V01_extIn">' +
'<mg:ListOfLoymemberserviceupdateAllMemberV01extIn><mg:UpdateAllMemberIn>' +
`<mg:CustomerNumber>${requestInput.membererId}</mg:CustomerNumber>` +
`<mg:MemberClass>${requestInput.loyProgramName}</mg:MemberClass></mg:UpdateAllMemberIn>` +
'</mg:ListOfLoymemberserviceupdateAllMemberV01extIn></cus:updateAllMember_V01_Input>'
}
}
}
module.exports = LoyaltyRestService

Now with the REST wrapper in place, instead of providing all the XML elements from WSDL for the soapenv:Envelope payload input of the SOAP WebService, enrolling a new member into a Loyalty campaign is as simple as running a POST call on the designated API endpoint. For example:

curl --header 'Content-Type: application/json' --request POST 'http://localhost:8080/loyaltyapi/v1/member/id/loyProgram/name'

Closing Thoughts

While SOAP webservices continue to stay relevant for enterprise system integrations, REST microservices provide a more meaningful and easier to use interface, being the architecture pattern of choice for cloud-based integrations. In this article we’ve seen how to use Node.js for building a bridge between these two worlds.

I’m grateful to Cosmin Avram, Naval Arya and Joerg Decker for the learning experience and making this API possible.

I saw the future — MetroNom solutions expo
A bridge between past and future technologies

--

--