Quickstart: Web Backend¶
This guide walks you through building a small FastAPI service that exposes ERLC server data over HTTP. By the end you will have four endpoints: GET /status, GET /players, GET /staff, and POST /announce.
1. Prerequisites¶
- Install the required packages:
- A PRC server key — see Clients and Authentication for how to obtain one.
2. Client lifecycle¶
Use FastAPI's lifespan context manager to start and close AsyncClient alongside the app. This replaces the deprecated @app.on_event("startup"/"shutdown") approach.
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, Header, HTTPException
from erlc_api import AsyncClient, CommandPolicy, CommandPolicyError, cmd
from erlc_api.cache import AsyncCachedClient
api = AsyncClient.from_env()
cached_api = AsyncCachedClient(api, ttl_s=5)
announce_policy = CommandPolicy(allowed={"h"}, max_length=120)
admin_token = os.environ["ERLC_ADMIN_TOKEN"]
@asynccontextmanager
async def lifespan(app: FastAPI):
await api.start()
yield
await api.close()
app = FastAPI(lifespan=lifespan)
3. Endpoints¶
GET /status — server overview¶
@app.get("/status")
async def status():
info = await cached_api.server()
return {
"name": info.name,
"players": info.current_players,
"max_players": info.max_players,
}
GET /players — list online players¶
@app.get("/players")
async def players():
online = await cached_api.players()
return [{"name": p.name, "team": p.team, "user_id": p.user_id} for p in online]
GET /staff — staff on duty¶
@app.get("/staff")
async def staff():
duty = (await cached_api.staff()).members
return [{"name": m.name, "role": str(m.role)} for m in duty]
POST /announce — broadcast a hint¶
from pydantic import BaseModel
class AnnounceBody(BaseModel):
message: str
@app.post("/announce")
async def announce(body: AnnounceBody, x_admin_token: str | None = Header(default=None)):
if x_admin_token != admin_token:
raise HTTPException(status_code=403, detail="Command access denied.")
try:
safe_command = announce_policy.validate(cmd.h(body.message))
except CommandPolicyError as exc:
raise HTTPException(status_code=400, detail=exc.result.reason) from exc
preview = await api.preview_command(safe_command, policy=announce_policy)
result = await api.command(preview.command, policy=announce_policy)
return {"ok": True, "command": preview.command, "message": result.message}
4. Error handling¶
Convert API errors to proper HTTP responses using HTTPException.
from erlc_api import AuthError, RateLimitError, ERLCError
from erlc_api.diagnostics import diagnose_error
@app.get("/status")
async def status():
try:
info = await cached_api.server()
return {"name": info.name, "players": info.current_players, "max_players": info.max_players}
except AuthError:
raise HTTPException(status_code=401, detail="Invalid server key.")
except RateLimitError as e:
raise HTTPException(status_code=429, detail=diagnose_error(e).to_dict())
except ERLCError as e:
raise HTTPException(status_code=502, detail=diagnose_error(e).to_dict())
5. Dashboard helpers¶
Use status, bundle presets, and multi-server helpers for routes that feed dashboards.
from erlc_api.multiserver import AsyncMultiServer, ServerRef
from erlc_api.status import StatusBuilder
@app.get("/dashboard")
async def dashboard():
bundle = await cached_api.bundle()
return StatusBuilder(bundle).build().to_dict()
servers = [
ServerRef("main", "main-server-key"),
ServerRef("training", "training-server-key"),
]
@app.get("/servers")
async def servers_view():
return await AsyncMultiServer(cached_api, servers, concurrency=3).aggregate()
AsyncCachedClient is intentionally read-only for caching. Keep POST
endpoints such as /announce calling api.command(...) directly.
6. Running the server¶
The API will be available at http://localhost:8000. FastAPI generates interactive docs at /docs automatically.
7. Common mistakes¶
- Using
@app.on_event("startup"/"shutdown"). These are deprecated in modern FastAPI. Use thelifespancontext manager instead. - Sharing one
AsyncClientinstance across threads. The client is not thread-safe; use it only within the async event loop FastAPI runs on. - Returning model objects directly.
erlc_apimodels are dataclasses, not Pydantic models. Call.to_dict()or map fields manually before returning from a route. - Not handling
RateLimitError. ERLC enforces per-endpoint limits. Without handling, FastAPI returns a 500 to the caller instead of a meaningful 429. - Starting the client outside
lifespan. Callingawait api.start()at module level runs before an event loop exists and will raise a runtime error. - Using
api.server(all=True)for every route. Use bundle presets or explicit includes so hot paths only request what they need. - Caching write endpoints. Cache helpers skip
command(...); keep command routes explicit. - Leaving command routes public. Protect them with authentication,
CommandPolicy, cooldown/rate limits, and audit logs.
8. Next steps¶
- Endpoint Reference — full list of available endpoints (
kill_logs,bans,vehicles, etc.) - Commands Reference — all supported in-game commands via
cmd.* - Workflow Utilities Reference — cache, status, bundle presets, diagnostics, and multi-server helpers
- Event Webhooks and Custom Commands — receive in-game events pushed to your backend
- Waiters and Watchers — poll for changes and build live-update features
Related Pages¶
Previous Page: Getting Started | Next Page: Quickstart: Discord.py