Quick Start Guide
Every great game begins with a solid server. Let's build yours.
This guide walks you through building a working Altruist server step by step — starting with HTTP, then adding real-time communication. Every example is copy-pasteable and testable.
Step 1: HTTP Server in 3 Files
Program.cs
using Altruist;
await AltruistApplication.Run(args);
That's it. Altruist will:
- Load
config.ymlfrom your output directory - Auto-discover and register all
[Service]classes - Auto-discover all controllers and portals
- Start the server based on your config
config.yml
altruist:
server:
http:
host: "0.0.0.0"
port: 3000
Note:
Make sure config.yml is copied to the output directory. In your .csproj file add:
<ItemGroup>
<None Update="config.yml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
HelloController.cs
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("hello")]
public class HelloController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok(new { message = "Hello from Altruist!", timestamp = DateTime.UtcNow });
}
}
Run and test
dotnet run
Open your browser to http://localhost:3000/hello or run:
curl http://localhost:3000/hello
You'll see:
{ "message": "Hello from Altruist!", "timestamp": "2025-01-01T12:00:00Z" }
Note:
That's a full Altruist HTTP server — no Startup.cs, no builder.Build(), no middleware registration. The framework discovers your controllers, configures Kestrel, and runs everything from config.yml.
Step 2: Add WebSocket Communication
Now add real-time packet handling. Update your config and create a portal.
Update config.yml
altruist:
server:
http:
host: "0.0.0.0"
port: 3000
transport:
codec:
provider: json
websocket:
enabled: true
path: /ws
persistence:
cache:
provider: inmemory
Define a Packet
Packets are the data structures sent between client and server:
using System.Text.Json.Serialization;
using Altruist;
using MessagePack;
[MessagePackObject]
public struct ChatPacket : IPacketBase
{
[Key(0)]
public uint MessageCode { get; set; }
[Key(1)]
[JsonPropertyName("text")]
public string Text { get; set; }
public ChatPacket()
{
MessageCode = 1000;
Text = string.Empty;
}
}
Note:
Framework packets use MessageCode 0–999. Your custom packets should start at 1000+.
Create a Portal
Portals are the entry points for client-server communication. Each portal handles events via [Gate] methods:
using Altruist;
[Portal("/ws")]
public class ChatPortal : AltruistPortal
{
private readonly IAltruistRouter _router;
public ChatPortal(IAltruistRouter router)
{
_router = router;
}
[Gate("chat")]
public async Task OnChat(ChatPacket packet, string clientId)
{
// Echo back to the sender
await _router.Client.SendAsync(clientId, new ChatPacket
{
MessageCode = 1000,
Text = $"Server received: {packet.Text}"
});
// Broadcast to all connected clients
await _router.Broadcast.SendAsync(new ChatPacket
{
MessageCode = 1000,
Text = $"[{clientId[..8]}]: {packet.Text}"
});
}
}
Run and test
dotnet run
You should see output like:
The Portals Are Open! Connect At:
----------------------------------------------------
Address: ws://0.0.0.0:3000/ws
Transport: WebSocket
----------------------------------------------------
Welcome, traveler!
Now listening on: http://0.0.0.0:3000
Both HTTP and WebSocket are running:
- http://localhost:3000/hello — your HTTP controller still works
- ws://localhost:3000/ws — real-time WebSocket endpoint
Test from your browser console
Open your browser's developer console (F12) and paste:
const ws = new WebSocket("ws://localhost:3000/ws");
ws.onmessage = (e) => console.log("Received:", e.data);
ws.onopen = () => {
console.log("Connected!");
ws.send(JSON.stringify({ messageCode: 4, event: "chat", text: "Hello world!" }));
};
Note:
The initial message uses messageCode: 4 (the framework's routing packet code) with an event field that routes to the matching [Gate] handler. The framework decodes the payload into your packet type (ChatPacket).
Step 3: Switch to TCP
The same portal works over TCP — just change the config:
altruist:
server:
http:
host: "0.0.0.0"
port: 3000
transport:
codec:
provider: json
tcp:
enabled: true
port: 9050
Now ChatPortal handles TCP connections on port 9050 instead of WebSocket — no code changes needed. You can even enable both at the same time:
transport:
codec:
provider: json
websocket:
enabled: true
path: /ws
tcp:
enabled: true
port: 9050
Note:
Portal code is transport-agnostic. Switching between WebSocket, TCP, and UDP is a single config change. This is one of Altruist's core design principles.
Multiple Portals
You can register multiple portals on the same path. Each handles different events:
[Portal("/ws")]
public class ChatPortal : AltruistPortal
{
[Gate("chat")]
public async Task OnChat(ChatPacket packet, string clientId) { ... }
}
[Portal("/ws")]
public class GamePortal : AltruistPortal
{
[Gate("join")]
public async Task OnJoin(JoinPacket packet, string clientId) { ... }
[Gate("move")]
public async Task OnMove(MovePacket packet, string clientId) { ... }
}
Both portals share the same connection endpoint but handle different events. The framework routes each incoming packet to the correct [Gate] based on the event field.
What's Next?
- Configuration — Full config.yml reference
- Portals & Gates — Deep dive into the portal system
- Packets — How to define and route packets
- Networking — WebSocket, TCP, and UDP transport setup