---
title: "How to Transition from SOAP to REST APIs"
description: "Learn how to transition from SOAP to REST APIs, simplifying development and enhancing performance with key steps and best practices."
canonicalUrl: "https://zuplo.com/learning-center/how-to-transition-from-soap-to-rest-apis"
pageType: "learning-center"
authors: "adrian"
tags: "API Design"
image: "https://zuplo.com/og?text=How%20to%20Transition%20from%20SOAP%20to%20REST%20APIs"
---
**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. If you're unfamiliar with these tools, we have an article on
    [API gateway proxies and load balancers](./2025-05-08-api-gateways-vs-load-balancers.md)
  - Stateless architecture simplifies scaling and reduces server resource usage.

- **Key Migration Steps:**
  1. **Inventory SOAP APIs**: Document functionality, dependencies, and usage.
  2. **Design REST APIs**: Use resource-based endpoints, JSON responses, and
     token-based authentication.
  3. **Use Tools**: Tools like
     [`soap-converter`](https://github.com/anhthang/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.
  4. **Test Thoroughly**: Validate functionality, performance, and security
     before deployment.
  5. **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\]](https://dev.to/keploy/soap-vs-rest-api-understanding-the-battle-of-web-services-5g9a).

### 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\]](https://dev.to/keploy/soap-vs-rest-api-understanding-the-battle-of-web-services-5g9a).

### 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\]](https://dev.to/keploy/soap-vs-rest-api-understanding-the-battle-of-web-services-5g9a).

- **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\]](https://www.upwork.com/resources/soap-vs-rest).
- **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](/learning-center/soap-vs-rest-apis-ultimate-showdown).

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`](https://www.crcind.com/csp/samples/%25SOAP.WebServiceInvoke.cls?CLS=SOAP.Demo&OP=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](https://www.chick-fil-a.com/) faced challenges due to undocumented
APIs, which disrupted development coordination
[\[3\]](https://blog.kodezi.com/api-inventory-a-step-by-step-tutorial).

You can use WSDL to document your SOAP APIs (we cover this in our
[SOAP API guide](/learning-center/a-developers-guide-to-soap-apis)).

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](https://www.crcind.com/csp/samples/SOAP.Demo.CLS?WSDL=1) that
defines all the data and services. Here's the abridged version

```xml
<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](https://www.nbc.ca/), build
RESTful models from SOAP services
[\[5\]](https://www.apimatic.io/blog/2018/12/api-transformer-recipes-facilitating-migration-from-soap-to-rest).
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`](https://github.com/anhthang/soap-converter) to convert from
SOAP to OpenAPI 3.1.x. Here's how to do it:

#### Install `soap-converter`

```bash
yarn global add soap-converter
# or
npm install -g soap-converter
```

#### Convert to OpenAPI 3.1

```bash
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):

```json
{
  "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](/learning-center/json-vs-xml-for-web-apis), 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

1. **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.

```ts
// 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();
}
```

2. **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:

```ts
// 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,
  });
}
```

3. **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.

4. **Supporting REST-ful requests**

Start supporting calls that pass the `zip` in a query parameter, rather than
using a `SOAP` envelope.

```ts
// 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,
  });
}
```

5. **Transitioning to Your New Service**

Now let's start making calls to the `getCity` service we created instead of the
original SOAP service.

```ts
// 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.

6. **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.

```ts
//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](https://zuplo.com/docs/policies/custom-code-outbound)
within Zuplo to selectively transform the response to SOAP depending on what the
client asks for:

```ts
// 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](/learning-center/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](https://portal.zuplo.com/signup?utm_source=blog)
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.