Switching from SOAP to REST APIs can simplify development, improve speed, and enhance scalability. REST's lightweight, stateless design and support for various data formats like JSON make it ideal for modern applications, especially in mobile and cloud environments. Here's a quick overview of the key steps and benefits:
-
Why Switch?
- REST uses smaller payloads (JSON vs. SOAP's XML) for faster performance.
- REST integrates easily with modern infrastructure like load balancers and proxies.
- Stateless architecture simplifies scaling and reduces server resource usage.
-
Key Migration Steps:
- Inventory SOAP APIs: Document functionality, dependencies, and usage.
- Design REST APIs: Use resource-based endpoints, JSON responses, and token-based authentication.
- Use Tools: Tools like
soap-converter
can convert WSDL to OpenAPI specs. An API management tool like Zuplo can make transitioning easier by giving you programmatic control over your API traffic. - Test Thoroughly: Validate functionality, performance, and security before deployment.
- Dual Support: Temporarily maintain both SOAP and REST to ensure a smooth transition.
SOAP vs REST: Core Differences#
Explore the differences between SOAP's structured approach and REST's adaptable, resource-oriented design.
Technical Structure#
SOAP operates under strict protocols, while REST is built around a resource-based architecture. This distinction has a direct impact on API design and upkeep. SOAP's structured standards can make implementation and updates more complex. Additionally, SOAP's tight client-server coupling demands in-depth knowledge during setup. On the other hand, REST's loose coupling allows for independent updates, making it more flexible for developers [1].
Data Format Options#
A standout feature of REST is its ability to work with various data formats. SOAP, in contrast, is limited to XML for all message formatting. Here's a quick comparison:
Format | SOAP | REST |
---|---|---|
JSON | No | Yes |
XML | Yes | Yes |
HTML | No | Yes |
Plain Text | No | Yes |
Binary | No | Yes |
REST's support for JSON is particularly useful, as it minimizes payload size and speeds up parsing [1].
State and Scale#
How state is managed is another major distinction. SOAP maintains state across transactions, which can complicate scaling and increase resource usage. REST, however, employs a stateless design, making it easier to distribute loads and scale efficiently [1].
- Resource Usage: REST's stateless nature typically requires fewer server resources.
- Caching: REST supports caching for improved performance, whereas SOAP requests cannot be cached [2].
- Performance: REST's lightweight payloads allow for quicker processing and faster response times.
If you're interested in more of the differences between REST and SOAP, check out our REST vs SOAP guide.
These differences play a crucial role in planning migrations, which will be detailed in the next section.
Step 1: Migration Planning Steps#
A well-thought-out plan ensures system stability during the shift from SOAP to
REST APIs. Let's walk through how we would migrate the
LookupCity
SOAP API to a REST API as an example.
1.1: Create an API Inventory#
Start by documenting all existing SOAP APIs. You can't transition all of your APIs if you don't have good accounting of them. A real-world example: Chick-fil-A faced challenges due to undocumented APIs, which disrupted development coordination [3].
You can use WSDL to document your SOAP APIs (we cover this in our SOAP API guide).
Include the following metadata for each API:
- Purpose and functionality
- Version and release history
- Technical specifications (ex. params)
- Usage policies
- Dependencies and integrations
Luckily for us, the LookupCity
API already has a
WSDL file that
defines all the data and services. Here's the abridged version
<s:element name="LookupCity">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" name="zip" type="s:string"/>
</s:sequence>
</s:complexType>
</s:element>
<s:element name="LookupCityResponse">
<s:complexType>
<s:sequence>
<s:element name="LookupCityResult" type="s0:Address"/>
</s:sequence>
</s:complexType>
</s:element>
...
<message name="LookupCitySoapIn">
<part name="parameters" element="s0:LookupCity"/>
</message>
<message name="LookupCitySoapOut">
<part name="parameters" element="s0:LookupCityResponse"/>
</message>
...
<operation name="LookupCity">
<input message="s0:LookupCitySoapIn"/>
<output message="s0:LookupCitySoapOut"/>
</operation>
...
<operation name="LookupCity">
<soap:operation soapAction="http://tempuri.org/SOAP.Demo.LookupCity" style="document"/>
<input>
<soap:body use="literal"/>
</input>
<output>
<soap:body use="literal"/>
</output>
</operation>
1.2: Define your REST API Structure#
The first iteration of your transition doesn't need to be perfect, but here are some key components you should consider when designing your REST APIs:
Component | Best Practice | Impact |
---|---|---|
Endpoints | Resource-based naming | Clearer and easier to maintain |
Response Format | Default to JSON | Faster parsing and smaller payloads |
Authentication | Token-based methods | Better security and scalability |
Documentation | OpenAPI specification | Consistent documentation and tooling support |
In the LookupCity
example, we can create a simple endpoint GET /cities
which
takes a query parameter zipcode
. The response will be an array of cities, and
when the zipcode
param is provided, only a single matching city will be in the
response.
1.3: Security Updates#
Replace WS-Security with modern REST authentication protocols like OAuth 2.0 and JWT for better security and authorization.
Key security measures include:
- Switching from XML encryption to HTTPS
- Using token-based authentication
- Setting up rate limiting and validating requests
- Configuring CORS policies
- Establishing API gateway security controls
These updates will help secure your REST APIs and ensure compliance with modern standards.
In our LookupCity
example, there is no security so let's not worry about it
for now.
Step 2: Format & Interface Migration#
Modern tools streamline the process of moving from SOAP to REST by automating key tasks, saving time, and reducing errors. These tools work alongside the planning and design strategies already discussed.
2.1 Specification Format Conversion#
Transforming WSDL specifications into OpenAPI/Swagger formats helps organizations, like the National Bank of Canada, build RESTful models from SOAP services [5]. It's not a perfect solution given SOAP design doesn't map 1:1 with REST API design, but it's a good way to kickstart the process.
We recommend using
soap-converter
to convert from
SOAP to OpenAPI 3.1.x. Here's how to do it:
Install soap-converter
#
yarn global add soap-converter
# or
npm install -g soap-converter
Convert to OpenAPI 3.1#
soap-converter -i https://graphical.weather.gov/xml/SOAP_server/ndfdXMLserver.php\?wsdl -t OpenAPI -v 3.1 -o ./weather.openapi.json
Here's what our LookupCity
conversion looks like (after a lot of cleanup):
{
"openapi": "3.1.0",
"info": {
"title": "SOAPDemo",
"description": "",
"version": "1.0.0"
},
"paths": {
"/LookupCity": {
"post": {
"summary": "Operation LookupCity",
"description": "",
"operationId": "LookupCity",
"requestBody": {
"content": {
"text/xml": {
"schema": {
"$ref": "#/components/schemas/LookupCityInput"
}
}
},
"required": true
},
"responses": {
"default": {
"description": "",
"content": {
"application/xml": {
"schema": {
"$ref": "#/components/schemas/LookupCityOutput"
}
}
}
}
}
}
}
},
"servers": [
{
"url": "/SOAPDemo"
}
],
"components": {
"schemas": {
"LookupCityInput": {
"description": "Input message for wsdl operation LookupCity",
"type": "object",
"properties": {
"Envelope": {
"type": "object",
"properties": {
"Body": {
"type": "object",
"properties": {
"LookupCity": {
"$ref": "#/components/schemas/LookupCity_element_s0"
}
},
"required": ["LookupCity"]
}
},
"required": ["Body"]
}
},
"required": ["Envelope"]
},
"LookupCityOutput": {
"description": "Output message for wsdl operation LookupCity",
"type": "object",
"properties": {
"Envelope": {
"type": "object",
"properties": {
"Body": {
"type": "object",
"properties": {
"LookupCityResponse": {
"$ref": "#/components/schemas/LookupCityResponse_element_s0"
}
}
}
},
"required": ["Body"]
}
},
"required": ["Envelope"]
},
"LookupCity_element_s0": {
"type": "object",
"properties": {
"zip": {
"type": "string"
}
}
},
"LookupCityResponse_element_s0": {
"type": "object",
"properties": {
"LookupCityResult": {
"type": "string"
}
},
"required": ["LookupCityResult"]
}
}
}
}
You'll notice a couple of key issues with the output that will need to be
addressed. First, the endpoint is a POST
which is a REST API anti-pattern
(requesting data should be a GET), but it is reflective of how SOAP is more akin
to RPC rather than resource-based. Second, the data format is still XML for
request and response bodies. Let's work on that...
2.2 Data Format Migration#
Switching from XML to JSON demands careful attention to data structures and types. We cover this in great detail in our JSON vs XML guide, but you will eventually want to transition all of your APIs to use JSON, especially if they are user-facing. There are two ways to approach this:
If Most of Your SOAP APIs are Stateful#
Well you're in a tough-bind honestly. You will need to either rearchitect your client application to not rely on server state, or incorporate a database within the steps below.
If Most of Your SOAP APIs are Stateless#
If most of your SOAP API operations do not heavily rely on state, you can do the following
- Rewrite your services to be resource and JSON-based
First, we want to create an internal service for getting equivalent data to the SOAP API.
I don't have to time to create a zipcode to city database, so I will simply proxy an existing one from USPS.
// getCity.ts
export default async function getCity(zip: string) {
let bodyContent = new FormData();
bodyContent.append("zip", zip);
let response = await fetch(
"https://tools.usps.com/tools/app/ziplookup/cityByZip",
{
method: "POST",
body: bodyContent,
},
);
return await response.json();
}
- Create a facade endpoint
Create a facade endpoint (using an API gateway or middleware) that will act as the transition layer between your SOAP service, and your new REST API. At first, configure your gateway to only route traffic to your old SOAP endpoint as we set up your new REST service. Here's an example using a Zuplo gateway route handler:
// lookupCity.ts
import { ZuploContext, ZuploRequest, HttpProblems } from "@zuplo/runtime";
export default async function (request: ZuploRequest, context: ZuploContext) {
// URL of the SOAP service
const url = "https://www.crcind.com/csp/samples/SOAP.Demo.CLS";
const requestText = await request.text();
return await fetch(url, {
method: "POST",
headers: {
"Content-Type": "text/xml;charset=UTF-8",
SOAPAction: "LookupCity",
},
body: requestText,
});
}
- Transition Clients to facade endpoint
You will need to modify call-sites of the old SOAP service to call our new REST-ful facade endpoint - but they can continue to send the same request bodies.
- Supporting REST-ful requests
Start supporting calls that pass the zip
in a query parameter, rather than
using a SOAP
envelope.
// lookupCity.ts
import { ZuploContext, ZuploRequest, HttpProblems } from "@zuplo/runtime";
export default async function (request: ZuploRequest, context: ZuploContext) {
const zip = request.query.zip;
if (!zip) {
// Same SOAP proxy code as above
...
}
const url = "https://www.crcind.com/csp/samples/SOAP.Demo.CLS";
// Construct a SOAP envelope to proxy the old service
const soapEnvelope = `<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:tem="http://tempuri.org">
<soapenv:Header/>
<soapenv:Body>
<tem:LookupCity>
<tem:zip>${zip}</tem:zip>
</tem:LookupCity>
</soapenv:Body>
</soapenv:Envelope>`;
return await fetch(url, {
method: "POST",
headers: {
"Content-Type": "text/xml;charset=UTF-8",
SOAPAction: "LookupCity",
},
body: soapEnvelope,
});
}
- Transitioning to Your New Service
Now let's start making calls to the getCity
service we created instead of the
original SOAP service.
// lookupCity.ts
import getCity from "./getCity"
const TRANSITION_TO_REST = false;
...
if (!zip) {
// Untouched SOAP proxy code
...
}
if (TRANSITION_TO_REST) {
let data = await getCity(zip);
return new Response(
`<?xml version="1.0" encoding="UTF-8" ?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:s='http://www.w3.org/2001/XMLSchema'>
<SOAP-ENV:Body>
<LookupCityResponse xmlns="http://tempuri.org">
<LookupCityResult>
<City>${data.defaultCity}</City>
<State>${data.defaultState}</State>
<Zip>${data.zip5}</Zip>
</LookupCityResult>
</LookupCityResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`,
{
headers: {'Content-Type': 'text/xml; charset=UTF-8'}
}
);
}
The TRANSITION_TO_REST
condition can be whatever you'd like, a boolean,
environment variable, or even a float from 0-1 whereby you compare the result of
a random number generation to that float and if its less than that value, you
use the SOAP service, if its greater, you use the REST service.
Upon testing out the new service and ensuring it behaves correctly, we can move on to the next step.
- Transition to JSON
We should change our gateway code to allow clients to request JSON responses as they transition from SOAP. First, let's get rid of any remaining SOAP code within the service itself, and consistently return JSON.
//lookupCity.ts
import { ZuploContext, ZuploRequest, HttpProblems } from "@zuplo/runtime";
import getCity from "./getCity";
export default async function (request: ZuploRequest, context: ZuploContext) {
const zip = request.query.zip;
if (!zip) {
return HttpProblems.badRequest(request, context);
}
let data = await getCity(zip);
return new Response(
JSON.stringify({
city: data.defaultCity,
state: data.defaultState,
zip: data.zip5,
}),
{
headers: { "Content-Type": "application/json" },
},
);
}
Then we can use a Custom Code Outbound Policy within Zuplo to selectively transform the response to SOAP depending on what the client asks for:
// json-to-soap.ts
export default async function policy(request: Request, response: Response) {
if (
response.headers.get("Content-Type") === "application/json" &&
request.headers.get("Accept").includes("xml")
) {
const jsonRes = await response.json();
const { city, state, zip } = jsonRes;
return new Response(
`<?xml version="1.0" encoding="UTF-8" ?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:s='http://www.w3.org/2001/XMLSchema'>
<SOAP-ENV:Body>
<LookupCityResponse xmlns="http://tempuri.org">
<LookupCityResult>
<City>${city}</City>
<State>${state}</State>
<Zip>${zip}</Zip>
</LookupCityResult>
</LookupCityResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`,
{
headers: { "Content-Type": "text/xml; charset=UTF-8" },
},
);
}
return response;
}
Now we can gradually transition all clients over from XML to JSON. Once
transition is complete - you can delete json-to-soap.ts
and you now have a
fully functioning REST API!
Step 3: Testing and Deployment#
Thorough testing ensures that REST APIs match the functionality of SOAP while improving performance and reliability.
3.1 Functional Testing Methods#
To confirm that REST endpoints align with SOAP functionality, use the following tests:
Test Type | Purpose | Key Validation Points |
---|---|---|
Contract Testing | Ensure API behavior matches specs | Request/response formats, status codes |
Integration Testing | Validate interactions between systems | Data flow, service dependencies |
Security Testing | Confirm data protection measures | Authentication, authorization, encryption |
Functional Testing | Test business logic accuracy | Expected outputs, error handling |
Testing should be conducted in environments that closely mimic production. Automated tests can help maintain consistency. Afterward, test the endpoints under load to confirm steady performance.
3.2 Speed and Resource Tests#
Performance testing helps assess how REST APIs handle load by focusing on:
- Response Time: Average time taken to respond under load.
- Throughput: Number of requests processed per second.
- Resource Usage: CPU, memory, and network consumption.
- Error Rates: Frequency of failed requests or timeouts.
You can check out our SOAP API Testing guide to learn about tools for testing your original SOAP API. Step CI is a good tool for testing either.
Conclusion#
Shifting from SOAP to REST APIs represents a key step forward in modern API development. REST's simpler design and ability to handle various data formats make it a better fit for today’s development needs. However, successfully making this shift requires careful planning and execution.
The transition process hinges on three main steps:
- Analysis: Review your current SOAP services, examine WSDL documentation, and evaluate system dependencies.
- Implementation: Map SOAP operations to HTTP methods, ensuring security protocols are upheld.
- Testing: Confirm the functionality and performance of the newly developed REST API.
Ready to start transitioning your SOAP APIs to REST? As demostrated by the tutorial above Zuplo's code-first API gateway make protocol-transitions a breeze. With code, you have full control over the data and services you use, and can create clean abstractions over your services to avoid code-duplication or inconsistencies across your API.
FAQs#
What are the key challenges in switching from SOAP to REST APIs, and how can you address them?#
Transitioning from SOAP to REST APIs can present a few key challenges, but with the right approach, these can be effectively managed. One major hurdle is the difference in data formats - SOAP relies on XML, which is more verbose and complex compared to the lightweight JSON format commonly used in REST APIs. Additionally, SOAP’s rigid structure and protocols can make it harder to adopt the more flexible and stateless nature of REST.
To address these challenges, consider using tools that simplify the process, like those that convert WSDL files into OpenAPI specifications, enabling easier integration into RESTful systems. Another helpful strategy is implementing an adapter or proxy service to translate REST requests into SOAP-compatible formats and vice versa, ensuring backward compatibility during the transition. By planning carefully and leveraging the right tools, you can make the migration smoother and more efficient.
What makes REST APIs more scalable and efficient than SOAP due to their stateless design?#
The stateless design of REST APIs enhances scalability and performance by ensuring that servers do not store any client-specific data between requests. This simplifies server architecture, making it easier to distribute workloads across multiple servers and implement load balancing.
Additionally, the lack of stored session data allows REST APIs to support caching, which reduces unnecessary server calls and speeds up response times. These features make REST APIs particularly well-suited for modern, high-traffic applications where scalability and efficiency are critical.
What key security practices should I follow when migrating from SOAP to REST APIs?#
When transitioning from SOAP to REST APIs, implementing strong security practices is essential to safeguard your data. Focus on input validation, access control, and secure communication.
- Input validation: Ensure all input parameters are checked for proper length, type, and format to prevent injection attacks like SQL injection or cross-site scripting. Reject any unexpected or harmful content.
- Access control: Protect your resources by enforcing strict authentication and authorization. Use protocols like OAuth 2.0 or JSON Web Tokens (JWT) to ensure only authorized users can access your APIs.
- Secure communication: Always use HTTPS to encrypt data in transit, protecting sensitive information like authentication credentials from interception.
By prioritizing these practices, you can ensure a smooth and secure transition to REST APIs while maintaining data integrity and user trust.