A highly opinionated template for creating real-time, online games using TypeScript! Quickly create mmo-style games using React + Phaser for rendering, Colyseus for websockets and Electron for native builds! Also has support for Progressive Web Apps (PWA). Oh, and lots and lots of Vite for builds and testing!
- Overview
- Third-party Dependencies
- Developer Quickstart
- Most Used Commands
- Working with the PostgreSQL DB
- Testing
- Load Testing
- Available Commands
- Deployment
- Cost Breakdown
desktop: Frontend rendering for the game written in TypeScript using Electron, React, Phaser, Colyseus and GraphQL. When built, compiles an executable that runs a version of Chromium to render the game.game-api: Backend server that handles the game state and data via WebSockets and GraphQL. Written in TypeScript with Colyseus, Express and Apollo GraphQL.web: Static webpage that can serve as a marketing site, devlog, roadmap, wiki etc. Written in Typescript with React and GraphQL. Could also be used to serve the Phaser/Colyseus game (with support for PWA).
core-game: Main logic for the game. Shareable for use on the server as well as the client. Client-side prediction (CSP) demo included.client-auth: Shared auth forms, hooks, etc. built with the localuipackage. Used by both the static webpage and Electron app.ui: Shared Tailwindcss theme and Shadcn/ui componentstypescript-config: Shared TypeScript configseslint-config: Shared ESlint configs
This project relies on Supabase for JWT authentication. They offer a very generous free tier (50k MAU) and a straight-forward developer experience. It's also open source, so you can self-host if the need arises!
You'll need to create a free tier project and add the relevant keys to your local environment. Keys can be found by navigating to your Supabase project, then from the sidebar, "Project Settings" > "Data API". Here you should see a few important sections: "Project URL", "Project API Keys" and "JWT Settings". Use the values from these sections to create the following files based on the .env.example files:
apps/desktop/.envapps/game-api/.envapps/web/.env
NOTE: You'll need to add Redirect URLs (under Authentication > URL Configuration) to send email links to the auth redirect route. For example, if your custom domain is https://ts-game.online, you should add: https://ts-game.online/auth/redirect*. You can also add http://localhost:4200/auth/redirect* for local development. Configure this here:
https://supabase.com/dashboard/project/<PROJECT_ID>/auth/url-configuration
I recommend also updating the Secure email change setting to false (under Authentication > Providers > Email). By default, Supabase sets this to true, which will require users to click a link in both the old and new emails. While this is a good security measure, it can be annoying if users can't access their old email for some reason.
- Turborepo recommends that you define environment variables for each "app" instead of trying to define them globally. This helps prevent sensitive env values from leaking across apps.
- Although vite has built-in mechanisms for ensuring certain env doesn't get exposed on the frontend, I find it messy to have env from the web app loaded on the backend, for example.
- To ensure caches miss when updating env values, you may need to update the
turbo.jsonsection fortasks.build.env. It should be noted that turborepo will infer anyVITE_-prefixed env.- This means that if you add more
VITE_WHATEVERvariables to eitherapps/desktop/.envorapps/web/.env, then you do NOT need to update theturbo.json. - However, if you add another variable to
apps/game-api/.env, then you SHOULD update thebuildtasks'envin theturbo.json. - NOTE: the
devtask does not need theenvfield since it'scachesetting is set tofalse.
- This means that if you add more
You can also quickly customize the auth emails using the templates under packages/client-auth/email-templates by navigating to:
https://supabase.com/dashboard/project/<PROJECT_ID>/auth/templates
If you are familiar with pnpm and docker-compose, you can skip to Useful Commands or quickly start development with:
pnpm i
pnpm db:start
pnpm db:sync
pnpm devWhen you run dev, you should see:
- Web page at http://localhost:4200
- Native desktop window with the game connected to ws://localhost:4204
- Colyseus playground at http://localhost:4204
- Colyseus monitor tool at http://localhost:4204/monitor
- Apollo GraphQL playground at http://localhost:4204/graphql
- PostgreSQL DB at postgresql://guest:guest@localhost:5432/game_db
Install the docker-compose cli, which can be installed via Docker Desktop. Make sure you have Docker Desktop running!
Ensure you are using the correct version of Node.js. You can validate this by comparing your local version of node (node -v) with the .nvmrc.
NOTE: The .nvmrc uses an alias for the node version. I highly recommend managing your local node version with nvm. This will allow you to quickly swap to the correct version with:
nvm useThis project uses pnpm for it's dependency mangement. You can install it with npm:
npm i -g pnpmThis project also uses Turborepo to manage scripts across the monorepo. While this is NOT necessary, it is recommended that you install a local version:
npm i -g turboWith the turbo cli, you can take a look at the project structure as well as the available commands:
turbo ls
turbo runThese commands are available from the root directory whether you decide to install the turbo cli locally or not...
NOTE: Commands should almost always be ran from the root directory. The package manager, pnpm, uses turbo to manage and run scripts. Since code can be shared between repos, turbo helps ensure that scripts run in a certain order when necessary.
| Command | Description |
|---|---|
pnpm db:[start|stop|sync] |
Uses docker-compose and prisma to manage a local PostgreSQL DB |
pnpm dev |
Run local development servers for each app |
pnpm db:test:[start|stop|sync] |
Uses docker-compose and prisma to manage a test PostgreSQL DB |
pnpm test |
Runs the typecheck, linter and tests for each repo |
pnpm test:watch |
Runs the test suite in each repo and watches for changes |
pnpm test:load |
Builds and runs the game-api then starts the load test |
pnpm preview |
Builds each app and runs a local server using the output |
pnpm build:[win|mac|linux] |
Builds the desktop app via Electron |
NOTE: If, for example, your Electron app is throwing an error when starting or building, but was previously working, try:
pnpm install:cleanIf this is your first time running the project, you'll need to start the DB with docker-compose and sync the tables with prisma:
pnpm db:start
pnpm db:syncpnpm generate:db-types will run during dev, build, etc., if you're using the monorepo commands.
However, if you change the DB schema via apps/game-api/prisma/schema.prisma, then you'll need to run db:sync again:
pnpm db:syncThis will generate a SQL migration, migrate your local DB, and update your types.
NOTE: This project uses Turborepo's Terminal UI (tui) and some tasks are interactive, such as test:watch and db:sync. When you want to interact with a window, press "i", then interact as normal. Press "ctrl" + "z" to leave interactive mode.
When you run pnpm test or pnpm test:watch, it will run the game-api test suite, which requires a local test DB to be running. This can be accomplished by following the instructions for working with the PostgreSQL DB, with the only difference being that you add test: to the db: commands, ie:
pnpm db:test:start
pnpm db:test:sync
pnpm db:test:stopNOTE: pnpm test:watch offers a similar experience to pnpm dev, in the sense that it will watch for graphql and prisma type changes, and hot-reload as needed. pnpm test acts as a complete CI check as it will run all the type generators, typechecks, linters, then finally tests.
Colyseus has a built-in load testing tool that can be used to test the scalability of the game rooms. The example load test is located in apps/game-api/test/load/test.ts. To start the load test, run:
pnpm test:loadNOTE: This will run the game-api CI checks, start the server in preview mode, then run the load test. The preview server is production-like, but pointed at a local test DB. Please ensure the test DB is running and synced, same as the testing instructions above:
pnpm db:test:start
pnpm db:test:sync| Command | Description |
|---|---|
pnpm install |
Installs dependencies for each repo |
pnpm install:clean |
Runs a script to clear builds, caches, deps, etc., then runs install |
pnpm test |
Runs the typecheck, linter and tests for each repo |
pnpm test:watch |
Runs the test suite in each repo and watches for changes |
pnpm test:load |
Builds and runs the game-api then starts the load test |
pnpm lint |
Runs the code linting check in each repo |
pnpm lint:fix |
Runs the linter and fixes code when possible |
pnpm check-types |
Runs the typescript check in each repo |
pnpm generate:gql-types |
Generates the GraphQL types in each repo |
pnpm generate:gql-types:watch |
Generates the GraphQL types and watches each repo |
pnpm generate:db-types |
Generates DB types via prisma.schema |
pnpm generate:db-types:watch |
Generates DB types via prisma.schema and watches for changes |
pnpm db:start |
Uses docker-compose to start a local PostgreSQL DB |
pnpm db:stop |
Uses docker-compose to stop the local PostgreSQL DB |
pnpm db:sync |
Uses prisma to manage the local DB based on the schema.prisma |
pnpm db:test:start |
Uses docker-compose to start a local PostgreSQL DB for testing |
pnpm db:test:stop |
Uses docker-compose to stop the local PostgreSQL DB for testing |
pnpm db:test:sync |
Uses prisma to manage the testing DB based on the schema.prisma |
pnpm dev |
Run local development servers for each app |
pnpm generate:app-icons |
Generates PWA/Electron icons from apps/web/public/logo.svg |
pnpm generate:pwa-assets |
Generates PWA assets from apps/web/public/logo.svg |
pnpm build |
Generates icons and builds each app including sub-repos |
pnpm preview |
Builds each app and runs a local server using the output |
pnpm build:win |
Builds the desktop app (via Electron) for Windows |
pnpm build:mac |
Builds the desktop app (via Electron) for MacOS |
pnpm build:linux |
Builds the desktop app (via Electron) for Linux |
The web hosting setup is based on GitHub Pages and GitHub Actions. To get started:
Go to your github repository, then Settings > Pages, select Source and choose GitHub Actions.
You can also add a custom domain via the
Custom domainfield. Make sure you follow the instructions to ensure you have the correct DNS records in place. Also ensure you selectEnforce HTTPSand update the/apps/web/public/CNAMEfile with your custom domain.
Now go to Settings > Environments, then select (or create if needed) the github-pages environment. Fill out the Environment secrets section according to your /apps/web/.env file.
NOTE: Before hosting the game-api, you won't have a valid VITE_API_URL. So for now, just use the development value: http://localhost:4204
Now that everything is setup, you can either push to main or manually trigger the Deploy Web to GitHub Pages workflow from the Actions tab.
The desktop app files will be hosted via GitHub Releases and GitHub Actions. To get started:
Go to your github repository, then Settings > Environments and create the github-releases environment. Fill out the Environment secrets section according to your /apps/desktop/.env file.
NOTE: Before hosting the game-api, you won't have a valid VITE_API_URL or VITE_WEBSOCKET_URL. So for now, just use the development values: http://localhost:4204 and ws://localhost:4204 respectively.
NOTE: You will also need to set the GH_TOKEN environment secret. This is used to authenticate with the GitHub API and update the release with the desktop app files as they are built. Generate a personal access token and make sure to give it permissions to read and write on Contents.
To sign and notarize builds for macOS, you'll need:
- a MacBook (Keychain Access app required)
- an Apple Developer account
- to be enrolled in the paid Apple Developer Program
You'll also need to set the following:
| Repository Secret | Description |
|---|---|
APPLE_ID |
Email address of the Apple Developer account |
APPLE_APP_SPECIFIC_PASSWORD |
Generated "App-Specific Password" (16 characters) |
APPLE_TEAM_ID |
Team ID of the Apple Developer account (10 characters) |
MAC_CERT_B64 |
Base64 encoded "Developer ID Application" certificate |
MAC_CERT_PASS |
Password for the certificate |
Setting your APPLE_ID should be easy, just use your email address that you'll use to log in at https://account.apple.com/. Once you log in, under "Sign-In and Security", you should see a section called "App-Specific Passwords". Create a new password and copy the value (this only shows once, 16 characters, ex: asdf-asdf-asdf-asdf). Now use that value to set the APPLE_APP_SPECIFIC_PASSWORD repository secret.
To find your APPLE_TEAM_ID, go to https://developer.apple.com/account and scroll down to "Membership details". You should see your 10 character "Team ID" here (ex: 1234ABCD56).
Getting a valid MAC_CERT_B64 (and MAC_CERT_PASS) is a bit more involved. You'll need a "Developer ID Application" certificate (a .p12 file) that you can base64 encode. Before you can create that certificate, you'll need to create a "Certificate Signing Request" (CSR) file. This can be done with Keychain Access:
- Open Keychain Access
- Click "Keychain Access" > "Certificate Assistant" > "Request a Certificate from a Certificate Authority"
- Create a CSR with:
- your Apple Developer email as the User Email Address
- Common Name set to your name or company name
- empty CA Email Address
- "Request is" set to "Saved to disk"
- Save the CSR file (
.certSigningRequestfile)
Now you can create the "Developer ID Application" certificate (and .p12 file) from https://developer.apple.com/account/resources/certificates/list
- Create a new Certificate
- Select "Developer ID Application" and click "Continue"
- DO NOT CHANGE the default selection for "Previous Sub-CA"
- Upload the CSR file you just created
- Create the Certificate
- Download the certificate (should be a
.cerfile) - Double click the
.cerfile to add it to your Keychain - Ensure that you see a checkmark with "This certificate is valid", otherwise, revisit the steps above
- Expand the certificate, select the cert and the private key, then click "Export 2 items..."
- Ensure you save it as a
.p12format - As you save this file, you'll be prompted to enter a password for the certificate. This is the
MAC_CERT_PASSthat you'll need to set as a repository secret. I recommend using a password manager to generate a secure password.
You should now have a valid .p12 file. You can base64 encode it, then copy the value, by running:
base64 < ~/path/to/cert_name.p12 | pbcopyFinally, set the MAC_CERT_B64 repository secret with the base64 encoded value (you can paste it directly from your clipboard).
To sign builds for Windows, you'll need an Azure "Artifact Signing Account", which is a hosted Certificate Authority service. This works similarly to the Apple Developer program and has similar pricing as well. To get started:
- Create/log in to your Azure account
- From the Azure Dashboard, search for "Artifact Signing Accounts"
- Create a new Artifact Signing Account
- After creating the account, you'll need to give the Artifact Signing Account permissions to perform Identity Verification. This can be done by selecting the Account, then going to "Access Control (IAM)" and adding the role assigment for "Artifact Signing Identity Verifier" to your user.
- Now go back to the Artifact Signing Account, and click on "Identity validation", select "Individual" (instead of "Organization"), then "New Identity" > "Public"
- Fill out the form with your information, create your new identity and wait for processing
- You will most likely see the status update to "Action Required". If you do, click on the Identity and you should see a link (ex: "Please complete your verification here")
- Complete the verification process, which will likely include uploading your ID and a selfie through a verification portal, as well as downloading the Microsoft Authenticator app
- You should now see your Individual Identity with a status of "Completed"
- Now go back to the Artifact Signing Account, and click on "Certificate profile"
- Click "Create" > "Public Trust"
- Fill out the form, selecting your new Individual Identity from the dropdown for "Verified CN and O", then create the profile
With a valid "Certificate Profile", you can now fill out your Azure signing information in the /apps/desktop/electron-builder.yml file:
win:
azureSignOptions:
endpoint: "<YOUR_ARTIFACT_SIGNING_ACCOUNT_URI>"
codeSigningAccountName: "<YOUR_ARTIFACT_SIGNING_ACCOUNT_NAME>"
certificateProfileName: "<YOUR_CERTIFICATE_PROFILE_NAME>"
publisherName: "<YOUR_CERTIFICATE_SUBJECT>"The last thing we need to do is create the "App registration" in Azure:
- From the Azure Dashboard, search for "App registrations"
- Click "New registration"
- Give it a name like
gh-electron-signerand create - From your new App Registration, you should be able to see two fields:
- "Application (client) ID" > set this as the
AZURE_CLIENT_IDrepository secret - "Directory (tenant) ID" > set this as the
AZURE_TENANT_IDrepository secret
- "Application (client) ID" > set this as the
- Now click on "Manage" > "Certificates & secrets"
- Click "New client secret" and create a new secret
- Ensure you copy the Value, as it will only show once!
Finally, you can set the AZURE_CLIENT_SECRET repository secret with the value you just copied.
The desktop app files can now be built, signed (macOS/win) and hosted in a GitHub Release! Simply create a Release with a new tag (such as v0.0.1) and the GitHub Action will kick off the build process for each OS. As each OS build completes, the Release will be updated with the desktop app files.
You should already have a Supabase project setup with JWT auth. So that piece is covered!
Supabase offers a hosted PostgreSQL DB at no cost (limited storage/CPU). You can choose any other provider you'd like, and depending on your needs, you may want to compare pricing with DigitalOcean or AWS RDS at actual scale. But for a small project, Supabase is a great option.
Follow these instructions to setup a hosted PostgreSQL DB with Supabase for Prisma. You'll need to setup your Prisma user in Supabase and turn off the Supabase Data API setting.
TODO: update env
TODO: create DigitalOcean Droplet
TODO: update env
A focal point of this project is to be as cost-effective as possible at the start. Here's a breakdown of the costs associated with running everything:
| Provider | Service | Cost |
|---|---|---|
| GitHub Actions | CI/CD | Free |
| GitHub Pages | Web Hosting | Free |
| GitHub Releases | Desktop App Hosting | Free |
| Supabase | Auth and DB | Free |
| DigitalOcean | Persistent Server | ~$5/month |
| Apple | macOS signing cert | ~$8/month ($100/year) |
| Microsoft Azure | Windows signing cert | $10/month |
TOTAL: ~$23/month or ~$280/year



