← All Articles

APIs From Zero: HTTP, REST, SOAP, GraphQL and gRPC

A ground-up map of HTTP, REST, SOAP, WSDL, XSD, GraphQL, gRPC and WebSockets: what each one is, what it sends, and how it tells you what it can do.

APIs From Zero: HTTP, REST, SOAP, GraphQL and gRPC

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

HTTP is the protocol underneath almost all of this. One side sends a request, the other sends back a response.

A request travels from client to server, and a response comes back

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.

JSON and XML

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

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.

MethodDoes
GETread
POSTcreate
PUTreplace the whole thing
PATCHchange part of it
DELETEremove

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

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

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.

WebSockets

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.

Server-Sent Events

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.

SOAP

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.

The anatomy of a SOAP envelope

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

WSDL

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:

  • what operations exist
  • what URL to call
  • what XML to send
  • what XML comes back
  • what type each field is
  • what SOAPAction value each operation needs

Written 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

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 request is rejected, the missing path segment is added, and it is accepted

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.

XSD

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.

The same call, six ways

That is all six. Here is the thing worth taking away from them: every one of those sections was asking for the same record.

The same request for order 123 shown as REST, GraphQL, gRPC, SOAP, SSE and WebSockets in turn

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.

The map

How HTTP, REST, SOAP, GraphQL, gRPC, SSE and WebSockets relate

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.

Which one should you use

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.

SituationReach forBecause
Public API for other peopleRESTLowest barrier, works with curl, and GET responses cache
Chat, presence, collaborative editingWebSocketsBoth sides send, constantly, in small messages
Live dashboard, notifications, progressSSEOnly the server talks, and it reconnects on its own
A screen assembling many different shapesGraphQLOne round trip instead of a dozen
Your own services talking to each othergRPCBinary, generated clients, streaming, no browser to worry about
The other side already decidedWhatever they runUsually SOAP, and usually not up for discussion
Moving files aroundPlain HTTPIt 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.

They are all answering the same question

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.

StyleContractData format
SOAPWSDL + XSDXML
gRPC.protoProtobuf
GraphQLGraphQL schemaJSON
RESTOpenAPI, if anyone wrote oneJSON

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.

By Aman Kumar2026-08-308 min read

Related reading