Skip to main content
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

Build docs developers (and LLMs) love