
Most APIs I touch are JSON over HTTP. Then I needed to push files into a system that only speaks SOAP, which had me looking up one term, then the term inside that one, until it was less work to just lay the whole set out in order.
That's what this is. HTTP underneath, the formats everything is packaged in, the styles built on top, and the file each style uses to say what it can do. SOAP gets the most room here because it has the most parts, not because it matters most.
One thing to watch for: every style below asks for the same thing, order 123. Same question, six costumes. That comparison is most of what separates them.
It runs bottom-up, so start wherever your knowledge runs out.
HTTP is the protocol underneath almost all of this. One side sends a request, the other sends back a response.
Written out, that exchange looks like this.
GET /orders/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer abc123
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 123,
"customer": "Aman",
"total": 4200
}
Three parts worth naming, because everything later is built out of them. The method (GET) says what you want to do. The path (/orders/123) says what you want to do it to. The headers carry everything else: who you are, what format you're sending, what format you want back.
A path plus a method is what people mean by an endpoint: the address of one operation.
GET /orders/123
POST /orders
DELETE /orders/123
/orders is the road. The method is what you do when you get there.
Hold on to the headers, though. SOAP puts something in there that REST does not, and it is the thing most likely to break your first request.
Both are just ways of packaging data. Here is the same order in each.
{
"id": 123,
"customer": "Acme Ltd",
"items": ["desk", "chair"]
}
<order>
<id>123</id>
<customer>Acme Ltd</customer>
<items>
<item>desk</item>
<item>chair</item>
</items>
</order>
JSON maps cleanly onto objects in most languages, which is a large part of why modern APIs prefer it. XML carries more structure and more ceremony. Nearly everything below uses JSON, with one exception further down that uses XML for all of it.
REST organises an API around resources, using URLs to name them and HTTP methods to act on them.
GET /orders/123
{
"id": 123,
"customer": "Acme Ltd",
"total": 4200
}
Creating one:
POST /orders
Content-Type: application/json
{
"customer": "Acme Ltd",
"total": 4200
}
The methods map to what you'd expect.
| Method | Does |
|---|---|
GET | read |
POST | create |
PUT | replace the whole thing |
PATCH | change part of it |
DELETE | remove |
REST is a style rather than a specification, which is why two REST APIs can look nothing like each other. There is no file that tells you what operations exist. You read the docs, or you guess.
One thing REST gets almost for free, which matters more than it sounds: a GET is cacheable. Browsers, proxies and CDNs already know how to store the answer and hand it back without troubling your server. Nothing else here gets that by default.
GraphQL flips who decides what comes back. One endpoint, usually /graphql, and the client asks for the exact fields it wants.
query {
order(id: 123) {
customer
total
}
}
{
"data": {
"order": { "customer": "Acme Ltd", "total": 4200 }
}
}
Ask for two fields, get two fields. That helps with the REST situation where one screen needs several calls and another gets back fields it throws away. The cost moves to the server, which now has to handle arbitrary query shapes.
gRPC is built for services talking to other services rather than browsers talking to servers. You define the contract in a .proto file.
service OrderService {
rpc GetOrder (GetOrderRequest) returns (Order);
}
message GetOrderRequest {
string order_id = 1;
}
From that, tooling generates client and server code in whatever language you need. Data goes over the wire as Protobuf, which is binary rather than text, so it is smaller and faster to parse than JSON. It runs on HTTP/2 and supports streaming in both directions.
The tradeoff is that you cannot read it. A gRPC call is not something you can eyeball in a browser or paste into curl, which is exactly why it stays inside backends.
REST, GraphQL and a plain gRPC call are all one request, one response. WebSockets instead hold the connection open so both sides can send whenever they want.
client → server subscribe to order 123
server → client status: packed
server → client status: shipped
server → client status: delivered
Nobody has to ask first, and either side can speak. That two-way part is the point, and it is what you want for chat, multiplayer, presence, collaborative editing, anything where the client is sending as constantly as the server is.
WebSockets get reached for constantly when only the server actually needs to talk. SSE is that case: a one-way stream over an ordinary HTTP request.
GET /orders/123/events
Accept: text/event-stream
data: {"status":"packed"}
data: {"status":"shipped"}
The connection stays open and the server writes to it whenever it likes. Because it is plain HTTP, it passes through proxies that block WebSocket upgrades, and the browser's EventSource reconnects on its own when the connection drops, which is code you would otherwise write yourself.
You cannot send anything back over it. That is the whole tradeoff. For a live dashboard, a progress bar or notifications, that is not a limitation, and you get a simpler thing.
That is the modern set. The last one is older, and the rest of this post is mostly about it, because it comes with three extra pieces the others do not have.
SOAP is a formal protocol rather than a style. Every message is an XML envelope with a fixed shape.

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetOrder>
<OrderId>123</OrderId>
</GetOrder>
</soap:Body>
</soap:Envelope>
The envelope wraps everything. The body holds the actual call. The operation name lives inside the body, not in the URL, which is the sharpest break from everything above. In REST the URL tells the server what you want. In SOAP every request usually goes to the same URL, and what you want is buried in the XML.
That raises an obvious question. If every call goes to one address, how does the server know which operation you meant? That is what the next two sections are about.
A WSDL is the contract. It is a machine-readable file describing every operation the service offers. SOAP does not strictly require one, and some services do not publish theirs, which is its own kind of problem.
It answers, without you having to ask anyone:
SOAPAction value each operation needsWritten out in plain terms, a WSDL for an order service says something like:
Service: OrderService
Endpoint: https://api.example.com/OrderService
Operation: GetOrder
Input: OrderId (string)
Output: Order
Operation: CreateOrder
Input: Customer (string), Total (decimal)
Output: OrderId (string)
Real WSDLs are far uglier than that, because they are XML describing XML. But the content is exactly this.
The payoff is that tooling can read it. Point a client generator at OrderService.wsdl and you get real functions:
client.get_order(order_id="123")
and it assembles the envelope for you. REST's equivalent is OpenAPI, with the difference that OpenAPI is optional and often nobody wrote one.
SOAPAction is an HTTP header naming the operation you want. The server uses it to route the request internally.
POST /FileTransferService HTTP/1.1
Content-Type: text/xml
SOAPAction: "http://example.com/IFileTransferService/UploadFile"
Here is the failure. Your XML is valid. Your endpoint is right. Your credentials are fine. And you get back:
ContractFilter mismatch at the EndpointDispatcher
What the server means is: you sent this to an address I own, but the Action you gave me doesn't match any operation I believe lives here.
Several things cause that. A wrong or missing SOAPAction is one. A binding or security mismatch between client and server produces the same error, and so can a namespace mismatch inside the body. The header is the cheapest one to check first.
When it is the header, it is often a missing path segment. This fails:
SOAPAction: "http://example.com/UploadFile"
and this works:
SOAPAction: "http://example.com/IFileTransferService/UploadFile"
The interface name has to be in there. You cannot guess that string, and there is no rule that derives it. It is written in the WSDL:
<operation name="UploadFile">
<soap:operation
soapAction="http://example.com/IFileTransferService/UploadFile"/>
</operation>
Which is one concrete reason to go and find the WSDL. It is the authoritative source of a string you have to copy exactly.
You will run into XSD next to WSDL. The split is clean.
WSDL describes the operations. XSD describes the data.
<xs:complexType name="Order">
<xs:sequence>
<xs:element name="OrderNumber" type="xs:string"/>
<xs:element name="Total" type="xs:decimal"/>
</xs:sequence>
</xs:complexType>
That says an Order is exactly these fields, in this order, with these types. Send Total before OrderNumber and a strict server will reject it.
That is all six. Here is the thing worth taking away from them: every one of those sections was asking for the same record.
REST puts the operation in the URL. GraphQL puts it in a query and lets you pick the fields. gRPC turns it into a function call. SOAP buries it in the body and tells the server which one you meant using a header. SSE asks once and lets the server keep answering. WebSockets do that in both directions at once.
The request is the same. What changes is where each style writes down what you are asking for.

None of these are alternatives to HTTP. They sit on top of it. gRPC is the least obvious member, because it needs HTTP/2 specifically and does not use paths and methods the way the others do, but it is still HTTP underneath.
Start by ignoring speed. For most applications the time goes on database work and on how many round trips it takes to paint a screen, not on how the bytes were encoded. Choosing gRPC for performance while making eight sequential calls is solving the wrong problem.
The questions that actually decide it are who consumes this, and who needs to speak first.
| Situation | Reach for | Because |
|---|---|---|
| Public API for other people | REST | Lowest barrier, works with curl, and GET responses cache |
| Chat, presence, collaborative editing | WebSockets | Both sides send, constantly, in small messages |
| Live dashboard, notifications, progress | SSE | Only the server talks, and it reconnects on its own |
| A screen assembling many different shapes | GraphQL | One round trip instead of a dozen |
| Your own services talking to each other | gRPC | Binary, generated clients, streaming, no browser to worry about |
| The other side already decided | Whatever they run | Usually SOAP, and usually not up for discussion |
| Moving files around | Plain HTTP | It already does this well |
A few things swing these arguments more than people expect.
Caching. REST hands you browser, proxy and CDN caching by doing nothing. GraphQL over POST gives that up and you rebuild it in application code. On read-heavy public data this dwarfs any gain from a smaller payload.
Browsers cannot speak gRPC. Not "it is awkward", they cannot. You need grpc-web and a proxy in front. That alone rules it out for a lot of frontend work.
Debuggability. REST you can curl from anywhere. gRPC needs grpcurl and the .proto. SOAP needs the WSDL in hand before you can form a single valid request.
GraphQL moves complexity rather than removing it. The client stops over-fetching and the server starts handling arbitrary query shapes, N+1 problems and query-cost limits. That is often a good trade. It is never a free one.
The arguments people have about these are usually about the surface: XML versus JSON, verbose versus terse, old versus new. That is the least interesting difference.
Every one of them has to answer the same question. What operations exist, and what shape goes in and out? They just answer it in different places.
| Style | Contract | Data format |
|---|---|---|
| SOAP | WSDL + XSD | XML |
| gRPC | .proto | Protobuf |
| GraphQL | GraphQL schema | JSON |
| REST | OpenAPI, if anyone wrote one | JSON |
SSE and WebSockets are absent from that table on purpose. They do not describe operations at all. They are pipes, and whatever you send down one needs a convention of its own that none of them give you.
What differs is how much you can rely on that middle column.
With gRPC the .proto is the source the code is generated from, so it exists by construction. With SOAP a WSDL is not strictly required, and a service can run without publishing one, but it is what tooling reads to build a client and where the exact SOAPAction values are written down. With REST it is fully optional, so plenty of APIs have no machine-readable description at all and you learn what they do by calling them and reading the errors.
That is most of the practical difference between these, and it is worth more attention than the XML versus JSON argument.
![Setting Up Mac for Development [May 2026]](/_next/image?url=%2Fcontent%2Fblogs%2Fmac-setup-2026.png&w=3840&q=75)
The current toolkit and the AI coding stack I'd put on a fresh Mac today. A year after my 2025 setup.
Engineering
A deep, structured, research-anchored dive into UI/UX onboarding that synthesizes current best practices, psychology insights, expert research, and real product examples.
Engineering
How I reduced image weight from 83.57 MB to 1.89 MB using WebP optimization and simple automation
Engineering