Junius L
July 30, 2023
9 min read
Junius L
July 30, 2023
9 min read
SOAP (Simple Object Access Protocol) is a messaging protocol used for exchanging structured information in web services. Node.js, being a powerful and popular server-side JavaScript runtime, offers several libraries for interacting with SOAP-based APIs. In this blog post, we'll explore how to consume SOAP web services in Node.js using the soap library, a widely-used and easy-to-implement solution.
Before diving into the tutorial, make sure you have Node.js and npm (Node Package Manager) or yarn installed on your machine. Familiarity with basic JavaScript concepts and working knowledge of web services will be beneficial.
Set up a Node.js Project Let's start by creating a new directory for our project and initializing a Node.js project with yarn:
mkdir soap-web-service-consumercd into the directory and run yarn init -y
cd soap-web-service-consumer && yarn init -yNow, let's install the necessary modules using yarn:
yarn add express cors dotenv ip morgan http-status-codes swagger-ui-express express-openapi-validator swagger-routes-express @apidevtools/swagger-parserWe'll use TypeScript for type checking and better development experience. Additionally, we'll set up nodemon to enable automatic server restart during development.
yarn add @types/cors @types/ip @types/morgan nodemon typescript concurrently -DWithin the soap-web-service-consumer directory, create an app folder and an app.ts file inside it. This file will contain the code for our Express application.
mkdir app && touch app/app.tsInside app.ts, let's build the Express app:
import express, { Application, Request, Response } from 'express';
import cors from 'cors'
import morgan from 'morgan';
export const App = async(): Promise<Application> => {
const app: Application = express();
app.use(express.json())
app.use(express.urlencoded({ extended: false}))
app.use(morgan('tiny'))
app.use(cors())
app.get('/', (req: Request, res: Response) => {
res.send('API is up and running')
})
return app;
}In the root directory create an index.ts file and import the App we created in previous step
touch index.tsimport 'dotenv/config'
import IP from 'ip'
import { App } from './app/app'
const PORT = process.env.PORT
App()
.then(app => app.listen(PORT))
.then(() => console.log(`App is running on http://${IP.address()}:${PORT}`))
.catch(e => {
console.log('App is failed start', e);
})In the root directory of your project, create a tsconfig.json file to configure TypeScript:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"rootDir": "./app"
},
"include": ["./app/**/*.ts"]
}Next, open package.json and add the following scripts:
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "concurrently \"tsc --watch\" \"nodemon --ignore ./dist/ -q dist/index.js\""
},
Let's go over our script
build - will be used to build our application and convert Typescript to JavaScript
start - starts the application
dev - is used for dev mode, this will watch our files for any changes and auto restart the server, if there's any changes
Let's start our application by running the following command
yarn devOpen your browser and navigate to http://localhost:8080 or http://YOUR_IP_ADDRESS:8080
You should see API is up and running message displayed
Now, let's create a SOAP client to consume the web service. We'll create a clients directory inside the app folder and add the calculator.ts file and wsdl directory to it:
mkdir app/clients
mkdir -p app/clients/calculator/wsdl
touch app/clients/calculator.tshead over to http://www.dneonline.com/calculator.asmx?wsdl and inspect the wsdl we are going to use in the project.
Download the wsdl from http://www.dneonline.com/calculator.asmx?wsdl and save it inside the wsdl directory we created earlier.
Paste the following code inside calculator.ts file
import {createClientAsync} from 'soap'
import path from 'path'
interface IParams {
intA: number;
intB: number;
}
const wsdl = path.resolve(__dirname, './wsdl/dneonline.com_calculator.asmx_wsdl.xml');
const url = 'http://www.dneonline.com/calculator.asmx?wsdl'
const setupClient = async() => {
return createClientAsync(wsdl, {}).then((client) => {
client.setEndpoint(url)
client.addSoapHeader({ 'soap:Connection': 'keep-alive'})
return client
})
}
export const addition = (params: IParams) => setupClient().then(client => client.AddAsync(params))Let's go over the code, the first two lines import both soap and path modules.
Here we create an interface named IParams which is the parameters that the soap service expects
interface IParams {
intA: number;
intB: number;
}Then we reference the wsdl we downloaded earlier using the path module and set the url, which points to our web service
const wsdl = path.resolve(__dirname, './wsdl/dneonline.com_calculator.asmx_wsdl.xml');
const url = 'http://www.dneonline.com/calculator.asmx?wsdl'And setup our client, which we will use to call the methods in the soap service. The soap service has four methods Add, Subtract, Multiply and Divide.
const setupClient = async() => {
return createClientAsync(wsdl, {}).then((client) => {
client.setEndpoint(url)
client.addSoapHeader({ 'soap:Connection': 'keep-alive'})
return client
})
}and the last line calls the Add method inside the soap service, we are using the client above to call Add method
export const addition = (params: IParams) => setupClient().then(client => client.AddAsync(params))The setupClient function returns a promise which has the four mentioned methods/function. Then we can access those functions, using the dot notation
for example client.AddAsync(1, 3), client.MultiplyAsync(2, 2)
Now, let's create a controller to handle the incoming requests and use the SOAP client to call the calculator's Add method. Create a controllers directory inside the app folder and add the calculator.ts file to it:
mkdir app/controllers && touch app/controllers/calculator.tsPaste the following code into calculator.ts:
import { Request, Response } from 'express';
import * as calculator from '../clients/calculator/calculator'
import { errorResponse, successResponse } from '../utils/helpers';
export const addition = async(req: Request, res: Response) => {
try {
const response = await calculator.addition({ intA: req.body.intA, intB: req.body.intB})
successResponse(res, `Successfully added ${req.body.intA} and ${req.body.intB}`, response)
} catch (error) {
errorResponse(res, error)
}
}This's a simple controller, which imports the calculator client and call the addition function. It also import two functions from util helpers, which we'll create next.
In the app directory, create a new directory called utils and a file called helpers.ts
mkdir app/utils && touch app/utils/helpers.tsand paste the following code.
import { Response } from 'express';
import { StatusCodes } from 'http-status-codes';
export const successResponse = (res: Response, msg: string, data: [] ) => {
res.status(StatusCodes.OK)
res.json({
success: true,
error: null,
message: msg,
// @ts-ignore
body: data?.[0]
})
}
export const errorResponse = (res: Response, error: any ) => {
const message = error.message || 'Something went wrong. Please try again later'
res.status(error.statusCode || error.code || StatusCodes.INTERNAL_SERVER_ERROR)
res.json({
success: true,
error: error,
message: message,
body: null
})
}Let's add route to our app/app.ts
import express, { Application, Request, Response } from 'express';
import cors from 'cors'
import morgan from 'morgan';
import { addition } from './controllers/calculator'; // imports the calculator controller
export const App = async(): Promise<Application> => {
const app: Application = express();
app.use(express.json())
app.use(express.urlencoded({ extended: false}))
app.use(morgan('tiny'))
app.use(cors())
// added new routes
app.post('/addition', addition);
app.get('/', (req: Request, res: Response) => {
res.send('API is up and running')
})
return app;
}Let's run the application and test it using curl or postman
curl --location 'http://localhost:8080/addition' \
--header 'Content-Type: application/json' \
--data '{
"intA": 2,
"intB": 1
}'We get the following response
{
"success": true,
"error": null,
"message": "Successfully added 2 and 1",
"body": {
"AddResult": 3
}
}That's it.
Now, let's create a routes directory inside the app folder and add an index.ts file to it:
mkdir app/routes && touch app/routes/index.tsPaste the following code into index.ts:
// app/routes/index.ts
import * as calculator from '../controllers/calculator';
export const routes = {
...calculator
};
Adding swagger to our api for docuemtation
Now, create the swagger.yml file inside the app/swagger directory:
mkdir app/swagger && touch app/swagger/swagger.ymland paste the following definitions
openapi: 3.0.3
info:
title: Calculator API
version: '1.0.0'
servers:
- url: '/api/v1'
paths:
/addition:
post:
summary: Performs addition on two numbers
operationId: addition
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Addition'
responses:
200:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/AddResponse'
components:
schemas:
AddResponse:
type: object
properties:
success:
type: boolean
error:
type: string
message:
type: string
body:
type: object
properties:
addResult:
type: number
Addition:
type: object
required:
- intA
- intB
properties:
intA:
type: number
intB:
type: numberIn the above schema we define one addition path /addition which uses a post method and accept two parameters, defined in the component section of the schema
Addition:
type: object
required:
- intA
- intB
properties:
intA:
type: number
intB:
type: numberand the response
AddResponse:
type: object
properties:
success:
type: boolean
error:
type: string
message:
type: string
body:
type: object
properties:
addResult:
type: numberTo learn more about this visit Open Api
In app.ts, update the code to connect the routes to Swagger and use express-openapi-validator middleware:
import express, { Application, Request, Response } from 'express';
import SwaggerParser from '@apidevtools/swagger-parser';
import {connector} from 'swagger-routes-express';
import * as OpenAPiValidator from 'express-openapi-validator'
import swaggerUI from 'swagger-ui-express'
import cors from 'cors'
import morgan from 'morgan';
import { routes } from './routes';
export const App = async(): Promise<Application> => {
const apiDescription = await SwaggerParser.validate('app/swagger/swagger.yml')
const connect = connector(routes, apiDescription)
const app: Application = express();
app.use(express.json())
app.use(express.urlencoded({ extended: false}))
app.use(morgan('tiny'))
app.use(cors())
app.use(OpenAPiValidator.middleware({
apiSpec: 'app/swagger/swagger.yml'
}))
app.get('/', (req: Request, res: Response) => {
res.send('API is up and running')
})
app.use('/api-docs', swaggerUI.serve, swaggerUI.setup(apiDescription))
connect(app)
return app;
}Now you can access the Swagger UI documentation at http://localhost:8081/api-docs/ and test the API's addition endpoint. Additionally, you can use tools like curl or Postman to make requests to the /addition endpoint and see the results.
Add the remaining functions, Subtract, Multiply and Divide.