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.