Sitemap
iamprovidence

iamprovidence

👨🏼‍💻 Full Stack Dev writing about software architecture, patterns and other programming stuff https://www.buymeacoffee.com/iamprovidence

Services Communication (HTTP, Long Polling, Server-Sent Events, WebSocket, GraphQL, SOAP, gRPC, WebHook, AMQP)

10 min readMay 27, 2025

--

Press enter or click to view image in full size

While designing an application in client-server architecture has become almost a standard, choosing the correct data transfer protocol is not always easy.

Today, we will examine the difference between browser-to-server and server-to-server communication, the challenges each transmission layer has to face, what protocols and techniques help overcome impediments, and which you should use.

Excited? Let’s begin.

Browser-To-Server Communication

Browser-to-Server Communication is the interaction between a user’s web browser (client) and a backend server.

This communication is essential for most web applications. Let's review what common challenges and techniques exist on this communication layer.

HTTP (Hypertext Transfer Protocol)

The most well-known and used protocol is HTTP. It allows a client to request (or push) data from the server.

Press enter or click to view image in full size

As you know, when you cruise through websites, your browser automatically sends HTTP requests, which makes it a default choice for almost any application.

It is simple, reliable, and broadly supported.

Long Polling

The limitations of HTTP appear when it comes to real-time updates.

With HTTP, only the client can communicate with the server. The browser doesn’t expose endpoints or listen for incoming connections. This means, if something happens on the server, it has no way to notify all clients.

To achieve real-time updates, developers often resort to a technique called long polling. The client repeatedly (e.g., every 5 seconds) sends an HTTP request to the server to check if there are any updates.

It gives a near-real-time experience with a simple implementation.

However, it is poorly optimized and potentially can lead to a self-DDoS attack.

Server-Sent Events

In case you need actual, real-time experience, you can use Server-Sent Events (SSE). It is an unidirectional, lightweight, and efficient way to push real-time updates from a server to clients.

SSE works on top of HTTP. The client opens a connection and receives a stream of events from the server.

In your backend, you need an endpoint that constantly sends these events:

app.MapGet("/sse", (CancellationToken cancellationToken) =>
{
var stream = GetServerEvents(cancellationToken);

return TypedResults.ServerSentEvents(stream);
});

static async IAsyncEnumerable<SseItem<NotificationDto>> GetServerEvents([EnumeratorCancellation] CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
yield return new SseItem<NotificationDto>(newNotification);

await Task.Delay(1_000, cancellationToken);
}
}

While on the client, you simply open the connection and process data:

const eventSource = new EventSource('http://localhost:2702/sse');

eventSource.onmessage = (event) => {
console.log(event.data);
};

The EventSource API automatically reconnects if the connection drops. You can send an event ID so the client can resume from where it left off.

Overall, SSE is ideal for cases where the server frequently sends updates (e.g., real-time notifications or monitoring).

WebSocket

WebSocket is a communications protocol, providing a full-duplex (two-way communication) channel over a single TCP connection.

Press enter or click to view image in full size

Similarly to SSE, your backend needs an endpoint that can handle and publish messages:

app.UseWebSockets();

app.Map("/ws", async context =>
{
if (!context.WebSockets.IsWebSocketRequest)
{
return;
}

var webSocket = await context.WebSockets.AcceptWebSocketAsync();

do
{
// handle incoming message
var receiveResult = await webSocket.ReceiveAsync();

// send message to clients
await webSocket.SendAsync(message);
} while (!receiveResult.CloseStatus.HasValue)
});

Usage on the client side is even simpler:

const ws = new WebSocket("ws://localhost:2702/ws")

// receive event
ws.onmessage = (event) => {
console.log(event.data)
}

// send data
socket.send("Hello, server!");

WebSocket is often used for live chats, collaborative tools (e.g. Google Docs), and multiplayer games.

GraphQL

With HTTP, you have a strict response. This causes a problem of over-fetching or under-fetching. Clients often receive too much or too little data and need multiple requests to get everything😒.

GraphQL is a protocol that provides a flexible request schema. Clients can specify exactly what data they need in one request.

Press enter or click to view image in full size

To make it work, a server should expose generic endpoints:

public class UsersController
{
public IQueryable<Platform> GetUsers()
{
return _dbContext.Users;
}

public async Task<AddUserResponse> AddUser(AddUserRequest request)
{
return await _dbContext.Users.Add(request);
}
}

That client can call it in an arbitrary way via HTTP:

const query = `
query {
users
{
id
name
age
}
}`;

fetch('http://localhost:2702/graphql', {
method: 'POST',
body: JSON.stringify({ query })
})

Even though GraphQL was designed to execute queries, you can also mutate data with it. Additionaly, it also supports bidirectional communication when using subscriptions over WebSockets.

GraphQL provides a flexible and efficient data loading API for complex data sources where a client can define the structure of the response to avoid over- or under-fetching.

Server-To-Server Communication

Server-to-Server Communication happens between two or more backend systems without direct user involvement.

It enables microservices to interact, third-party APIs to be called, and background jobs to run. While you can still use Long Polling, WebSocket, GraphQL, and other protocols, other techniques have dominated the field.

HTTP (Hypertext Transfer Protocol)

Yes, I know 😅. I could not resist.

You can send HTTP requests from the code, making it also suitable for mobile apps, microservices, or any other use cases.

The server simply needs to define an HTTP endpoint:

[ApiController]
[Route("api/greeter")]
public class GreeterController : Controller
{
[HttpPost]
public HelloResponse SayHello(HelloRequest request)
{
var response = new HelloResponse
{
Message = "Pong",
};

return response;
}
}

On the other side, the client can make requests using an HttpClient:

var httpClient = new HttpClient();

var request = new HelloRequest
{
Message = "Hello world",
};

var response = await httpClient.PostAsJsonAsync("http://localhost:2702/api/greeter", request);

var result = await response.Content.ReadFromJsonAsync<HelloResponse>();

As before, HTTP is a good choice if you are targeting a RESTful architecture or building a public API for a broad third-party audience.

SOAP (Simple Object Access Protocol)

SOAP protocol is considered to be legacy, but can still be found in old enterprise projects.

Get iamprovidence’s stories in your inbox

Join Medium for free to get updates from this writer.

It is an XML-based protocol built on top of HTTP that allows one microservice to call another.

The client needs to read the WSDL (Web Services Description Language) file to identify which operations are allowed on the server.

<message name="HelloRequest">
<part name="name" type="xs:string"/>
</message>

<message name="HelloResponse">
<part name="message" type="xs:string"/>
</message>

<portType name="Greeter">
<operation name="SayHello">
<input message="HelloRequest"/>
<output message="HelloResponse"/>
</operation>
</portType>

The server, of course, needs to define this endpoint:

[WebService]
public class GreeterService : WebService
{
[WebMethod]
public HelloResponse SayHello(HelloRequest request)
{
. . .

var response = new HelloResponse
{
Message = "Pong",
};

return response;
}
}

Which can be called by the client:

var client = new MySoapClient(MySoapClient.EndpointConfiguration.MySoapPort);

var request = new HelloRequest
{
Name = "Ping",
};

var result = await client.SayHello(request);

SOAP offered a lot, but most developers preferred faster, simpler, and lighter alternatives — especially as the web and mobile development exploded.

gRPC (Google Remote Procedure Call)

While microservices can communicate using HTTP, it has become more popular to use gRPC for machine-to-machine (M2M) communication.

RPC has been around for a while, but this one is built by Google and is on top of HTTP 2.

gRPC operates similarly to HTTP, however, it is high-performing and efficient due to binary serialization (protobuf is used). Making it ideal for microservices.

Additionaly, gRPC supports bidirectional communication with both server- and client-streaming.

Before using gRPC, both client and server need to agree on the structure of the payload and API. This is done through Interface Definition Language (IDL).

A proto file (hello.proto) has to be defined:

syntax = "proto3";

message HelloRequest {
string name = 1;
}

message HelloResponse {
string message = 1;
}

service Greeter {
rpc SayHello (HelloRequest) returns (HelloResponse);
}

Based on this file, the server can autogenerate and implement GreeterService :

public class GreeterService : Greeter.GreeterBase
{
public override async Task<HelloResponse> SayHello(HelloRequest request, ServerCallContext context)
{
. . .

var response = new HelloResponse
{
Message = "Pong",
};

return response;
}
}

gRPC-client is also created and is used by the client app:

var grpcClient = new Greeter.GreeterClient(GrpcChannel.ForAddress("https://localhost:2702"));

var request = new HelloRequest
{
Name = "Ping",
};

var response = await grpcClient.SayHelloAsync(request);

The main use case for gRPC is internal service-to-service communication, like microservices.

WebHook

When your client is another server and you need to notify it of some event, you can simply call the server back over HTTP or gRPC.

While this approach is fine for simple cases, it doesn’t scale when you have thousands of third-party applications wanting to receive events. Hardcoding all their server addresses isn’t practical — you need a dynamic solution. This is where WebHooks come in.

The concept is straightforward: the server maintains a list of all clients in a database. When an event occurs, it simply sends an HTTP request to each registered client.

Typically, the server exposes two endpoints: one for registering applications and another that triggers event publication.

// should be in database
public static List<Subscription> _subscriptions = new List<Subscription>();

// subscribe
[ApiController]
[Route("web-hooks")]
public class WebHookController : Controller
{
[HttpPost]
public void Subscribe(WebHookSubscribeRequest request)
{
var subscription = new Subscription
{
CallbackUrl = request.CallbackUrl,
};

_subscriptions.Add(subscription);
}
}

// publish
[ApiController]
[Route("orders")]
public class OrderController : Controller
{
[HttpPost]
public void Create(CreateOrderRequest request)
{
var newOrder = new Order();
_db.Orders.Add(newOrder);

// dispatch events
var event = new OrderCreatedEvent(newOrder);
foreach (var sub in _subscriptions)
{
httpClient.PostAsJsonAsync(sub.CallbackUrl, event);
}
}
}

This time, the client has to define the callback endpoint that will consume and handle events:

[ApiController]
[Route("callback-url")]
public class WebHookCallbackController : Controller
{
[HttpPost]
public void Handle(object event)
{
. . .
}
}

This is basically an implementation of the Observer pattern on a larger scale.

The technique is useful when you need to address asynchronous real-time notifications to external systems.

AMQP (Advanced Message Queuing Protocol)

WebHooks are an ideal choice where you can’t control the infrastructure; however, for internal communication in complex systems, you have another option to exchange events.

You often need to execute complex, resource-intensive tasks that you would better do asynchronously instead of trying to fit them in a short-lived HTTP request. This is where queues shine.

Both client and server can publish events in the event bus, where those are stored until consumed.

Press enter or click to view image in full size

Even though a queue can be built on top of SQL Server, it often requires additional infrastructure like message brokers.

It is not a standard, and depends on particular implementation, but in terms of usage, the application just needs to add events to this queue:

_queue.Add(new OrderCreatedEvent());

Which will be processed in the order they are added:

foreach (var event in _queue)
{
// handle event
. . .
}

The queues are good when you need to do some processing in the background.

They also decouple applications. It is easier to add a new microservice since the producer and consumer do not know about each other.

Sum It Up

You have seen different protocols of data exchange and their differences. Regardless of the variety, the choice is quite easy to make:

💻 Whether you are working with MPA or SPA:

  • HTTP will be your default choice in most cases
  • Long polling is a practical approach to simulate near-real-time updates
  • for lightweight real-time needs, Server-Sent Events offer a simple and effective solution
  • you are unlikely to use WebSocket directly, but libraries like SignalR leverage it to build a real-time interactive app

🌐 If you’re choosing a protocol for internal service communication, your decision hinges on whether the interaction needs to be synchronous or asynchronous:

  • synchronous (use HTTP or gRPC)
  • asynchronous (messages with AMQP)

🌎When designing an API intended for public use, consider the following guidelines:

  • use HTTP as the primary protocol because it’s standardized, widely supported, and easy for clients to consume
  • for real-time updates, consider using WebHooks, allowing your API to push data to client endpoints as events occur

📡 When consuming a third-party API, you will likely face:

  • HTTP or GraphQL for synchronous communication
  • Long polling or WebHooks can be used to monitor changes asynchronously

🧩 You might sometimes encounter GraphQL and SOAP as alternatives to RESTful HTTP APIs, but it does not happen that often, so do not worry 😅.

Press enter or click to view image in full size

Give this article a clap or a few if you like it 👏
You can support me with a link below ☕️
And don’t forget to follow ✅
See U😉

--

iamprovidence
iamprovidence

Written by iamprovidence

👨🏼‍💻 Full Stack Dev writing about software architecture, patterns and other programming stuff https://www.buymeacoffee.com/iamprovidence