Generally, REST and WebSockets are both means of ensuring communication between two or more applications. In this article, we are going to look into them in detail to understand their individual strengths, and weaknesses and when to choose one over the other.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

Understanding REST

REST (or Representational State Transfer) is an API architectural principle or style. It utilizes HTTP requests to perform standard database functions, also known as CRUD, on a particular resource – entity, or collection.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

In a RESTful context, clients do not interact directly with the resource’s raw data. Instead, they can reach the resources through their representations. This approach separates the client from complex backend implementations.

Let’s implement a simple method for POST request:

[HttpPost]
public IActionResult AddTask([FromBody] string task)
{
    if (string.IsNullOrEmpty(task))
    {
        return BadRequest("Enter valid data.");
    } 

    Tasks.Add(task); 
            
    return Ok("Task added successfully.");
}

This is a REST endpoint representing the HttpPost verb. From our code, the resource representation is a simple JSON string, task. It encapsulates the current state of the resource and presents it to clients. In return, the client sends a request to add a new task to the resource by inputting a text for the task parameter.

The server responds with the status of the request – BadRequest if the parameter is null or empty or Ok if the task was added successfully.

Resource representations can take various forms, such as JSON, XML, HTML, or other data formats. Typically, any data sent outside the specified format or state is considered invalid.

Note that while REST encourages well-defined representations, the handling of invalid data is typically determined by the specific API’s design and validation rules. 

Characteristics of REST

In the context of RESTful APIs, communication is characterized as both stateless and unidirectional. This approach adheres to a request-response model in which client applications take the initiative to communicate with the server. Each client request is self-contained, encompassing all the necessary information for the server to comprehend and execute the requested action. The key aspect of statelessness within REST is that the server does not retain any memory or awareness of prior client requests or sessions. As a result, each interaction between the client and server remains entirely independent.

To enhance server response times and overall application scalability, REST supports caching. Caching involves storing and reusing previously fetched or computed data, which can significantly reduce the need for repetitive, resource-intensive processing. By caching responses, servers can deliver frequently requested data more swiftly, improve overall application performance, and reduce the load on server resources. Caching is particularly beneficial when data doesn’t change frequently and we can use it for multiple clients, promoting efficiency and responsiveness.

REST is an excellent choice for exposing APIs to enable other applications to interact with your system. Its adherence to HTTP standards makes it particularly beneficial when you need to perform operations like creating, reading, updating, and deleting data in a database.

APIs that conform to these principles are known as REST or RESTful APIs.

If you’d like to learn more about implementing top-notch RESTful APIs, check out our article Top REST API Best Practices.

Disadvantage of REST

All things considered, however, REST is not the optimal choice for real-time applications where constant updates are critical such as live web chats or trading applications.

Owing to its unidirectional state of communication, current updates in a RESTful API would require repeated requests sent to the server at different intervals by the client. Clients can only make new requests after the server has responded to the previous ones. Over time, this will take a toll on the server.

For such applications, we have alternative protocols and architectures, such as WebSockets.

Now, we will delve into the world of WebSockets and understand how they address the specific needs of real-time communication.

Understanding WebSockets

WebSocket technology provides a seamless way to construct real-time applications while avoiding the overhead typically associated with traditional APIs. This approach enables bidirectional and stateful communication between the client and server by maintaining a persistent, long-lived connection. Hence, the two apps can send data to each other without repeated polling required in REST. There are no templates to organize the type of data sent, therefore clients can send data to the server without the need for an explicit request.

Let’s create a new ASP.NET Core application so we can define a WebSocket endpoint.

But first, we have to set up WebSocket middleware in the Program class:

...
builder.Services.AddSwaggerGen();

builder.WebHost.UseUrls("http://localhost:5289");
...

app.UseWebSockets();

app.Run();

After calling the app.UseWebSockets() method, our application is configured to recognize and manage WebSocket requests, allowing us to handle WebSocket communications within our application.

In the controller, let’s implement Get(), a WebSocket endpoint:

[Route("/ws")]
[HttpGet]
public async Task Get()
{
    if (HttpContext.WebSockets.IsWebSocketRequest)
    {
        using var ws = await HttpContext.WebSockets.AcceptWebSocketAsync();

        while (true)
        {
            var message = "The time is: " + DateTime.Now.ToString("HH:mm:ss");
            var bytes = Encoding.UTF8.GetBytes(message);
            var arraySegment = new ArraySegment<byte>(bytes, 0, bytes.Length);
            if (ws.State == WebSocketState.Open)
            {
                await ws.SendAsync(arraySegment,
                WebSocketMessageType.Text,
                true,
                CancellationToken.None);
            }
            else if (ws.State == WebSocketState.Closed || 
                     ws.State == WebSocketState.Aborted)
            {
                break;
            }
            Thread.Sleep(1000);
        }
    }
    else
    {
        HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
    }
}

Notice /ws, the route path and HttpGet attribute.

In the Get() method, we check if the incoming request is a WebSocket request using the IsWebSocketRequest property. 

If true, we then use the AcceptWebSocketAsync() method to accept the connection, creating an object, ws.

In the while loop, we repeatedly send a message with the current time in “HH:mm:ss” format to the connected WebSocket client. We encode the message as UTF-8 bytes and send it to the client using the SendAsync() method. If we close or abort the WebSocket connection, the loop breaks.

Now, let’s consume this endpoint in the Program class of a Console application:

var ws = new ClientWebSocket();

await ws.ConnectAsync(new Uri("ws://localhost:5289/ws"),
    CancellationToken.None);

Console.WriteLine("Connected!");

var receiveTask = Task.Run(async () =>
{
    var buffer = new byte[1024];
    while (true)
    {
        var result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer),
            CancellationToken.None);

        if (result.MessageType == WebSocketMessageType.Close)
        {
            break;
        }

        var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
        Console.WriteLine("Received: " + message);
    }
});

await receiveTask;

First, we have a new instance of a ClientWebSocket. After successfully establishing the WebSocket connection, it prints “Connected!” to the console. We run an asynchronous operation to receive messages from the WebSocket server. As long as the connection persists, we repeatedly print the time to the console.

Characteristics of WebSockets

WebSocket is a communication protocol. Endpoints that implement WebSockets do not require polling. They operate on an event-driven model, delivering data promptly as it becomes available.

Like a phone call, the connection enabled by WebSockets persists until either party ends it. Data goes back and forth seamlessly. Additionally, the server does not require the client to resend previously shared data because the server is enabled to retain state.

Due to this persistent connection over the request/response cycle of regular HTTP APIs, WebSockets require lower latency and less bandwidth. This reduces the load on the server.

WebSockets enable a duplex or bidirectional communication. This means that clients and servers send data to each other concurrently. 

Disadvantages of WebSockets

WebSocket is a relatively recent technology. Most modern browsers are compatible with it. However, older browsers and non-browser clients are not compatible with WebSockets.

Additionally, the implementation and integration of WebSockets are quite complex. We are responsible for managing the connection’s lifecycle, and errors, and ensuring we handle messages properly.

Although the persistent connection is a distinctive advantage WebSocket holds over REST, insufficient management and the absence of a set time-out pose a significant risk of prolonged idle open connections. Consequently, their stateful nature and the maintenance of persistent connections contribute to resource and memory consumption.

Comparing REST and WebSockets in C#

Let’s see a side-by-side comparison of REST and WebSockets:

RESTWebSocket
REST is stateless.WebSocket is stateful.
REST APIs are compatible with most browsers and can easily be integrated.Might require extra client/server support upon integration.It is also not compatible with some old browsers.
Best suited for CRUD operations on resources.Best for real-time updates.
Can be scaled using additional server instances to handle increasing load.Requires less server-side scaling for large numbers of concurrent clients since they maintain persistent connections.
Incurs more overhead due to the stateless nature of communication.Overhead is lesser because initial communication set up occurs just once.

Conclusion

It is of the utmost importance to select the right means of communication for our application. In general, WebSockets often take the spotlight, particularly in real-time applications. However, our choice between REST and WebSockets hinges on our application’s specific use cases and performance requirements. These considerations encompass data transfer patterns, statefulness, security needs, compatibility, resource handling, use cases, and scalability. As a result, our mode of communication must closely align with our vision for our application.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!