Building a Production-Grade Chat Application with ASP.NET Core, SignalR, Redis, and React


Introduction

Building a basic chat application is relatively simple. Building a production-grade chat platform capable of handling thousands of concurrent users, real-time messaging, message persistence, scalability, security, and fault tolerance is an entirely different challenge.

In this guide, we'll build a modern chat application using:

  • ASP.NET Core 9
  • SignalR
  • Entity Framework Core
  • SQL Server
  • Redis
  • JWT Authentication
  • React
  • Docker
  • Azure/AWS Deployment

What Makes a Chat Application Production Grade?

Most tutorials stop after sending messages between two browsers.

A production system requires:

  • Authentication
  • User Presence
  • Private Messaging
  • Group Chats
  • Message Persistence
  • Offline Messages
  • Read Receipts
  • Typing Indicators
  • Scalability
  • Monitoring
  • Security
  • Rate Limiting
  • Distributed Architecture

Final Architecture

React Frontend
       │
       ▼
ASP.NET Core API
       │
       ├── JWT Authentication
       ├── SignalR Hub
       ├── Message Service
       └── User Presence Service
              │
              ▼
Redis Backplane
              │
              ▼
SQL Server

Step 1: Create Solution Structure

Create separate projects.

ChatApp
│
├── ChatApp.API
├── ChatApp.Application
├── ChatApp.Domain
├── ChatApp.Infrastructure
└── ChatApp.Tests

Benefits:

  • Clean Architecture
  • Separation of concerns
  • Easier testing
  • Better maintainability

Step 2: Design Database Schema

Users

CREATE TABLE Users
(
    Id UNIQUEIDENTIFIER PRIMARY KEY,
    Username NVARCHAR(100),
    Email NVARCHAR(200)
)

Conversations

CREATE TABLE Conversations
(
    Id UNIQUEIDENTIFIER PRIMARY KEY,
    CreatedAt DATETIME2
)

Messages

CREATE TABLE Messages
(
    Id UNIQUEIDENTIFIER PRIMARY KEY,
    ConversationId UNIQUEIDENTIFIER,
    SenderId UNIQUEIDENTIFIER,
    Content NVARCHAR(MAX),
    SentAt DATETIME2,
    IsRead BIT
)

Indexes:

CREATE INDEX IX_Messages_ConversationId
ON Messages(ConversationId)

Without indexing, message retrieval becomes a bottleneck.

Step 3: Implement JWT Authentication

Install:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Configure JWT.

builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters =
            new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true
            };
    });
  • Why JWT?
  • Stateless
  • Scalable
  • Cloud-friendly
  • Mobile-friendly

Step 4: Configure SignalR

Install package:

dotnet add package Microsoft.AspNetCore.SignalR

Create Hub:

public class ChatHub : Hub
{
    public async Task SendMessage(
        string conversationId,
        string message)
    {
        await Clients.Group(conversationId)
            .SendAsync("ReceiveMessage", message);
    }
}

Register:

app.MapHub<ChatHub>("/chatHub");

Now clients receive messages instantly.

Step 5: Persist Messages

A common beginner mistake:

  • Messages exist only in memory.
  • Production systems always persist messages.
public async Task SaveMessageAsync(Message message)
{
    _db.Messages.Add(message);

    await _db.SaveChangesAsync();
}

Benefits:

  • History
  • Auditing
  • Offline access
  • Compliance

Step 6: Create Conversation Management

Users need chat rooms.

public async Task JoinConversation(
    string conversationId)
{
    await Groups.AddToGroupAsync(
        Context.ConnectionId,
        conversationId);
}

SignalR groups make room-based messaging easy.

Step 7: Add User Presence Tracking

Users expect online indicators.

Store active connections.

ConcurrentDictionary<Guid,string>

Track:

  • Online
  • Offline
  • Last Seen
  • Broadcast updates.
await Clients.All
    .SendAsync("UserOnline", userId);

Step 8: Implement Typing Indicators

Modern chat applications display typing status.

public async Task Typing(
    string conversationId)
{
    await Clients.Group(conversationId)
        .SendAsync(
            "UserTyping",
            Context.UserIdentifier);
}

This dramatically improves user experience.

Step 9: Read Receipts

Store message status.

public bool IsRead { get; set; }

When users open a conversation:

message.IsRead = true;

Notify sender:

await Clients.User(senderId)
    .SendAsync("MessageRead");

Step 10: Handle Offline Messages

Real users disconnect frequently.

Never assume users remain connected.

Workflow:

  • Save message
  • Check recipient status
  • Deliver immediately if online
  • Queue if offline

When reconnecting:

LoadUnreadMessages()

This ensures message reliability.

Step 11: Scale with Redis

Single-server SignalR works fine initially.

Problems occur when:

  • Multiple servers exist
  • Load balancing is introduced
  • Install Redis package.
dotnet add package Microsoft.AspNetCore.SignalR.StackExchangeRedis

Configure:

builder.Services
    .AddSignalR()
    .AddStackExchangeRedis(
        "localhost:6379");

Redis synchronizes messages across servers.

Step 12: Add Rate Limiting

Protect against spam.

builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter(
        "chat",
        config =>
        {
            config.PermitLimit = 50;
        });
});

Benefits:

  • Prevent abuse
  • Protect infrastructure
  • Improve stability

Step 13: Secure the Application

Security checklist:

Validate Inputs

message.Length <= 2000

Prevent XSS

Sanitize user content.

Encrypt Connections

Use HTTPS only.

Secure Secrets

Never store secrets in source control.

Use:

  • Azure Key Vault
  • AWS Secrets Manager

Step 14: Add Logging

Install Serilog.

dotnet add package Serilog.AspNetCore

Log:

  • Connections
  • Disconnections
  • Errors
  • Authentication failures

Example:

_logger.LogInformation(
    "User connected: {UserId}",
    userId);

Logs are critical for troubleshooting.

Step 15: Monitor Performance

Production systems need observability.

Track:

  • Message delivery latency
  • Failed messages
  • Active users
  • CPU
  • Memory

Recommended tools:

  • OpenTelemetry
  • Grafana
  • Prometheus
  • Application Insights

Step 16: Dockerize Everything

Dockerfile:

FROM mcr.microsoft.com/dotnet/aspnet:9.0
WORKDIR /app

COPY . .

ENTRYPOINT ["dotnet","ChatApp.API.dll"]

Benefits:

  • Consistent deployments
  • Easier scaling
  • Cloud portability

Step 17: Deploy to Cloud

Recommended deployment:

Azure App Service
        │
Azure Redis Cache
        │
Azure SQL Database
        │
Application Insights

Alternative:

AWS ECS
    │
ElastiCache Redis
    │
RDS SQL Server

Step 18: Load Testing

Before launch:

Use:

  • k6
  • JMeter
  • Locust

Test:

  • 1,000 users
  • 10,000 users
  • Peak traffic

Measure:

  • Latency
  • Throughput
  • Resource usage

Step 19: Add Advanced Features

Once the core system is stable:

Message Reactions

👍 ❤️ 🔥

File Uploads

  • Images
  • Videos
  • Documents

Voice Messages

Audio recording support

Push Notifications

Mobile alerts

End-to-End Encryption

Enhanced privacy

Common Production Mistakes

Avoid:

  • Storing messages in memory
  • No authentication
  • No monitoring
  • No Redis scaling
  • No indexing
  • No rate limiting
  • Logging sensitive data
  • Ignoring load testing

Conclusion

Building a production-grade chat application involves much more than sending messages between connected users. A reliable system must address scalability, persistence, security, observability, fault tolerance, and user experience.

By combining ASP.NET Core, SignalR, Redis, SQL Server, JWT authentication, and modern cloud infrastructure, you can create a messaging platform capable of supporting real-world workloads while maintaining high performance and reliability.

The techniques covered in this guide mirror the architecture patterns used by many modern messaging platforms and provide a strong foundation for building enterprise-ready real-time applications.

0 Comments Report