Skip to content

Technology Stacks

MIP is built across two distinct environments — the Unreal Engine game client/server and the Node.js backend — connected by a common communication layer. This page catalogues every major technology in the stack, what role it plays, and how the pieces fit together.


Overview

┌──────────────────────────┐       ┌──────────────────────────┐
│  GAME CLIENT (UE5)       │       │  DEDICATED SERVER (UE5)  │
│  VaRest ──► REST API     │       │  Socket.IO (role=server) │
│  Socket.IO (role=gc)     │       │  Agones SDK (health/rdy) │
└────────────┬─────────────┘       └────────────┬─────────────┘
             │                                  │
             └──────────────┬───────────────────┘
                  ┌─────────────────────┐
                  │  NestJS Backend     │
                  │  Port 3000          │
                  └─────────┬───────────┘
              ┌─────────────┼─────────────┐
              ▼             ▼             ▼
           MongoDB        Redis      Kubernetes API
           (data)     (cache/lock/   (Agones fleet +
                        pub-sub)      allocation)

Unreal Engine 5

Version: UE 5.7 Targets: Windows (client), Linux (dedicated server)

The game is split into two build targets:

  • Client — runs the player's game, handles rendering, input, UI, and Socket.IO/REST communication with the backend.
  • Dedicated Server — runs the authoritative game world, loads player data from the backend on join, and writes data back on logout or at timed intervals.

Key UE-side integrations

Integration Purpose
VaRest HTTP plugin used for REST calls (login, register)
Socket.IO Client (UE) WebSocket plugin; game client connects as role=gc, game server as role=server
Gameplay Ability System (GAS) Abilities, attributes, gameplay effects, status effects
Enhanced Input Input mapping and context management
Agones SDK (UE) Game server lifecycle reporting from inside UE (health pings, ready/shutdown signals)

Custom Control Channel

MIP replaces the default Unreal Engine control channel with UMIPControlChannel to intercept the NMT_Login handshake and verify the single-use JWT before admitting any player connection. This is registered in DefaultEngine.ini:

[/Script/OnlineSubsystemUtils.IpNetDriver]
!ChannelDefinitions=ClearArray
+ChannelDefinitions=(ChannelName=Control, \
    ClassName=/Script/ModularInventoryPlus.MIPControlChannel, \
    StaticChannelIndex=0, bTickOnCreate=true, \
    bServerOpen=false, bClientOpen=true, \
    bInitialServer=false, bInitialClient=true)

Info

JWT verification is skipped when WITH_EDITOR is defined, so Play-In-Editor works without a running backend.


NestJS

Runtime: Node.js Language: TypeScript Port: 3000

NestJS is the framework for the MIP backend (mip-be). It provides structured modules, dependency injection, guards, and middleware that map cleanly to the backend's responsibilities: REST API, WebSocket gateway, Kubernetes integration, and data persistence.

Modules

Module Responsibility
UsersModule Registration, login, JWT issuance, character selection data
CharactersModule Character CRUD and family/character relationships
FamilyModule Family (account) creation and lookup
SaveModule Item read/write, saveable object persistence
SocketIoModule Socket.IO gateway, event handlers, session routing
PlayersModule Player session state and routing logic
FriendsModule Friend requests, accept/reject, whisper routing
KubernetesModule Agones fleet creation, GameServer allocation, watch loop
RedisCacheModule Redis client, Redlock, pub/sub, session key helpers
AuthorizationModule JWT strategy, guards, global auth pipe

REST API

The API is prefixed with /api. Key endpoints:

Method Path Auth Purpose
POST /api/users/register None Create a new account
POST /api/users/login None Authenticate; returns session JWT
POST /api/users/login/editor None Editor-only login (skips some checks)
GET /api/users/me Bearer JWT Return character selection data
GET /api/users/me/editor Bearer JWT Editor variant of above
POST /api/users/editor-join-token Bearer JWT Generate a join token for PIE

Authentication

All authenticated REST endpoints use Passport + JWT strategy. The @NoAuth() decorator exempts public endpoints (register, login) from the global auth guard.

Login flow: 1. POST /api/users/login validates credentials via LocalAuthGuard (username/password). 2. On success, UsersService.login() calls JwtService.sign() with the user payload. 3. The returned JWT is used as a Bearer token for all subsequent REST calls and as the Authorization credential when opening the Socket.IO connection.


Socket.IO

Library: socket.io v4 (server), matching UE client plugin Adapter: @socket.io/redis-adapter (horizontal scaling via Redis)

Socket.IO handles all real-time game communication. Two distinct roles connect to the same gateway:

Role Token Connected by
gc (game client) Player session JWT UE game client after login
server Server JWT (embedded in container annotations) UE dedicated server on startup

The Redis adapter allows multiple backend instances to share the same Socket.IO event bus — a message sent from one backend pod is received by all other pods and forwarded to the correct socket.

Key event categories

Client → Backend

Event Payload Effect
character_selection Returns account, family, character list
join_game { characterId } Validates character, resolves map, issues single-use JWT, emits gate_travel
allocate_dungeon { mapName, additionalJsonString } Calls Agones allocation API, adds player to awaiting list
quit_dungeon Routes player back to previous persistent area
chat { message, channel } Validates and broadcasts to channel
friend_request / accept_friend / reject_friend / unsend_friend Friend identifiers Friend management

Server → Backend

Event Payload Effect
im_ready Session info Server signals readiness; backend dispatches awaiting players
load_player_server Character info Backend loads and emits player data (items, currencies, saveables, temp objects)
write_items Item/saveable data + save tag Backend persists data to MongoDB under distributed lock
gate_travel Map name Routes a player to a new server
verify_join_game_token JWT Backend validates single-use token; returns 1 or 0
session_pong Health check response

Backend → Client / Server

Event Recipient Purpose
gate_travel Client Provides server URL + single-use JWT for map travel
read_items Server Character and family storage items
read_currencies Server Character and family currencies
read_savable_objects Server Quests, abilities, and other saveable components
read_temp_duration_objects Server Cooldowns, temporary effects
chat Client(s) Incoming chat message
global_notice All clients Server-wide announcement
enhance_notice All clients Equipment enhancement broadcast
friend_request / accept_friend / reject_friend Client Friend event delivery

MongoDB

Driver: Mongoose v6 (@nestjs/mongoose) Default port: 27017 Container: mongo:latest (mip_mongo_db_local)

MongoDB is the primary persistent store for all player data. The schema is document-oriented, which maps naturally to MIP's JSON-serialized game data.

What is stored

Collection Contents
Users Account credentials (hashed with bcrypt), userId
Characters Character data, class, lastAreaMap, lastTransform
Families Family (account-wide) name and character list
Items Per-character and per-family inventory documents
Saveable objects Quests, abilities, and any SaveGame-marked component data
Temp duration objects Cooldowns, timed effects
Currencies Character and family currency values
Friends Friend relationships and pending requests

Data format

All game component data is serialized to JSON (matching Unreal's SaveGame property serialization). This means adding a new persisted property to a component requires only marking it SaveGame — the framework serializes and deserializes it automatically.


Redis

Client: ioredis v5 Distributed lock: Redlock v4 Default port: 6379 Container: redis:latest (mip_redis_local)

Redis serves three distinct roles in MIP:

1 — Session Store

All live session state is held in Redis, not MongoDB. This keeps lookups fast and avoids hitting the database on every Socket.IO event. Redis keys are namespaced by function (see the key namespace reference).

2 — Distributed Lock (Redlock)

When a game server writes player data (write_items), the backend acquires a Redlock distributed lock before reading from and writing to MongoDB. This prevents race conditions when a player disconnects and reconnects rapidly, or when two save events arrive simultaneously.

Two lock configurations are used:

Instance Retry count Retry delay Use case
redlock 10 100 ms Normal save/load operations
noRetryRedlock 0 Fire-and-forget locks (e.g., one-time token consumption)

3 — Pub/Sub Bus

Redis pub/sub connects backend instances and decouples the Kubernetes watch loop from the Socket.IO gateway. When the Kubernetes watcher detects a GameServer transitioning to Ready, it publishes to the gameservers_ready channel. The Socket.IO service subscribes and dispatches travel events to awaiting players.


Docker

Compose file: docker-compose.yml Local image: mip-be:local (built by scripts/deploy-docker-local.bat)

Docker packages both the backend and the game server binary into reproducible images. The local compose stack brings up three containers:

Container Image Port
mip_backend_local mip-be:local 3000
mip_redis_local redis:latest 6379
mip_mongo_db_local mongo:latest 27017

The local backend image is built in two stages (Dockerfile-local): 1. Builder — installs all dependencies and compiles TypeScript to dist/. 2. Runtime — copies only dist/, production node_modules, and required runtime files (KUBECONFIG-LOCAL, .env.local, yamls/).


Kubernetes + Agones

Local: Minikube Production/Staging: K3S Agones version: agones.dev/v1 K8s client: @kubernetes/client-node v0.20

The KubernetesService connects to the cluster using an embedded kubeconfig (the KUBE_CONFIG_PATH env variable). On startup it:

  1. Creates the mip-server-fleet Fleet (if it does not already exist).
  2. Creates the FleetAutoscaler for automatic buffer management.
  3. Opens a long-lived watch on the gameservers resource in the default namespace. When a GameServer transitions to Ready, it publishes to Redis.

Fleet configuration (local)

apiVersion: "agones.dev/v1"
kind: Fleet
metadata:
  name: mip-server-fleet
spec:
  replicas: 1
  template:
    spec:
      ports:
        - name: game
          portPolicy: Dynamic
          containerPort: 7777
          protocol: UDP
        - name: beacon
          portPolicy: Dynamic
          containerPort: 12345
          protocol: UDP
      template:
        spec:
          containers:
            - name: mip-server
              image: docker.io/library/mip-server:latest
              imagePullPolicy: IfNotPresent

Dungeon allocation

When a player requests a dungeon, KubernetesService.allocateDungeon() patches the allocation YAML with the session ID, map name, and a signed JWT, then calls:

k8sCustomObjectsApi.createNamespacedCustomObject(
  'allocation.agones.dev', 'v1', 'default', 'gameserverallocations', resource
)

Agones atomically selects a Ready server from the fleet, transitions it to Allocated, and injects the annotations as environment variables into the running container.


JWT (JSON Web Tokens)

Library: jsonwebtoken v9 / @nestjs/jwt Secret: JWT_SECRET environment variable

MIP uses three distinct JWT types:

Type Issued by TTL Purpose
Session JWT Backend on login Long-lived Client auth for REST + Socket.IO
Server JWT Backend on allocation Long-lived Game server identity with Socket.IO
Single-use join JWT Backend on join_game 120 s One-time gate travel authorization

The single-use join JWT is double-protected: it has a cryptographic expiresIn and its playerSessionId claim must be present in Redis. Verification deletes the Redis key immediately, so the token cannot be reused even if intercepted.


Stack Summary

Layer Technology Version
Game engine Unreal Engine 5.7
Backend framework NestJS 8.x
Language TypeScript 4.x
Database MongoDB 6 (Mongoose)
Cache / lock / pub-sub Redis + Redlock ioredis 5, Redlock 4
Real-time transport Socket.IO 4.x
Containerization Docker
Orchestration (local) Minikube
Orchestration (prod) K3S
Game server management Agones v1
HTTP client (UE) VaRest
Auth JWT (Passport) jsonwebtoken 9
Password hashing bcrypt 5.x