5Stack is built as a modern, client-side rendered application with a GraphQL-powered backend. The architecture emphasizes real-time capabilities, type safety, and modular design.
High-Level Architecture
The system consists of three main layers:
Frontend Layer Nuxt 3 application with Vue 3 components, client-side rendering (SSR disabled)
API Layer Hasura GraphQL engine providing real-time subscriptions and queries
Infrastructure Cloudflare Workers for edge functions, WebRTC for peer-to-peer communication
Application Structure
The frontend follows Nuxt 3’s convention-based directory structure:
source/
├── components/ # Vue components (manual imports only)
├── composables/ # Reusable composition functions
├── stores/ # Pinia state management stores
├── graphql/ # GraphQL query/mutation/subscription helpers
├── pages/ # File-based routing
├── layouts/ # Layout components
├── plugins/ # Nuxt plugins (Apollo, Monaco, etc.)
├── middleware/ # Route middleware
├── assets/ # Static assets and styles
├── public/ # Public static files
├── generated/ # Auto-generated Zeus GraphQL types
└── web-sockets/ # WebSocket and WebRTC utilities
Key Design Patterns
Client-Side Rendering
The application uses client-side rendering (CSR) for maximum interactivity:
// nuxt.config.ts
export default defineNuxtConfig ({
ssr: false , // Fully client-side application
// ...
})
SSR is disabled to support real-time features like WebRTC, live matches, and WebSocket connections that require browser APIs.
Type-Safe GraphQL
The application uses GraphQL Zeus for complete type safety from database to UI:
// graphql/meGraphql.ts
import { Selector } from "@/generated/zeus" ;
import { playerFields } from "./playerFields" ;
export const meFields = Selector ( "players" )({
... playerFields ,
name_registered: true ,
role: true ,
profile_url: true ,
matchmaking_cooldown: true ,
current_lobby_id: true ,
language: true ,
country: true ,
teams: [
{},
{
id: true ,
name: true ,
short_name: true ,
role: true ,
},
],
});
Types are generated from the Hasura schema using the Zeus codegen tool:
zeus https:// $NUXT_PUBLIC_API_DOMAIN /v1/graphql ./generated --ts --td
Reactive State Management
Pinia stores provide reactive state with TypeScript support:
// stores/AuthStore.ts
export const useAuthStore = defineStore ( 'auth' , () => {
const me = ref < InputType < GraphQLTypes [ 'players' ], typeof meFields >>();
const hasDiscordLinked = ref < boolean >( false );
async function getMe () : Promise < boolean > {
// Fetch and subscribe to user data
}
return { me , getMe , hasDiscordLinked };
});
Real-Time Communication
5Stack uses multiple protocols for real-time features:
GraphQL Subscriptions Real-time data updates via WebSocket (match status, player updates, etc.)
WebRTC Peer-to-peer communication for voice chat and latency testing
GraphQL Subscriptions
Real-time updates are handled through GraphQL subscriptions:
// Example subscription from AuthStore.ts
function subscribeToMe ( steam_id : string , callback : () => void ) {
const subscription = getGraphqlClient (). subscribe ({
query: generateSubscription ({
players_by_pk: [
{ steam_id },
meFields ,
],
}),
});
subscription . subscribe ({
next : ({ data }) => {
me . value = data ?. players_by_pk ;
callback ();
},
});
}
WebRTC Integration
WebRTC is used for peer-to-peer features like latency testing:
// Example from MatchmakingStore.ts
async function getLatency ( region : string ) {
const buffer = new Uint8Array ([ 0x01 ]). buffer ;
const datachannel = await webrtc . connect ( region , ( data ) => {
if ( data === "" ) {
datachannel . send ( buffer );
return ;
}
const event = JSON . parse ( data );
if ( event . type === "latency-results" ) {
datachannel . close ();
latencies . value . set ( region , event . data );
}
});
datachannel . send ( "latency-test" );
}
Application Entry Point
The main application component sets up global functionality:
<!-- app.vue -->
< script setup lang = "ts" >
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill" ;
import { useBranding } from "~/composables/useBranding" ;
useBranding ();
</ script >
< template >
< NuxtPwaManifest />
< StreamGlobal />
< template v-if = " me " >
< PlayerNameRegistration />
< MatchmakingConfirm />
</ template >
< NuxtLayout >
< NuxtPage ></ NuxtPage >
</ NuxtLayout >
< Toaster />
</ template >
Progressive Web App
The application is configured as a PWA with offline support:
// nuxt.config.ts - PWA configuration
pwa : {
injectRegister : 'auto' ,
registerType : 'autoUpdate' ,
workbox : {
maximumFileSizeToCacheInBytes : 10 * 1024 * 1024 ,
globPatterns : [ '**/*.{js,css,html,ico,png,svg,webp,woff}' ],
},
manifest : {
name : '5stack' ,
short_name : '5stack' ,
theme_color : '#000000' ,
background_color : '#000000' ,
display : 'standalone' ,
},
}
Security & Authentication
Authentication is handled through Steam OAuth with cookie-based sessions:
Credentials are included in all GraphQL requests
WebSocket connections authenticate via connection params
Role-based access control (RBAC) is enforced at the API layer
// Example role checking from AuthStore.ts
const roleOrder = [
e_player_roles_enum . user ,
e_player_roles_enum . verified_user ,
e_player_roles_enum . streamer ,
e_player_roles_enum . match_organizer ,
e_player_roles_enum . tournament_organizer ,
e_player_roles_enum . administrator ,
];
function isRoleAbove ( role : e_player_roles_enum ) {
if ( ! me . value ) return false ;
const meRoleIndex = roleOrder . indexOf ( me . value . role );
const roleIndex = roleOrder . indexOf ( role );
return meRoleIndex >= roleIndex ;
}
Next Steps
Tech Stack Explore the technologies powering 5Stack
GraphQL API Learn about the GraphQL API architecture
State Management Understand Pinia stores and reactive state
Components Component architecture and patterns