A progressive Node.js framework for building efficient and scalable server-side applications.
A NestJS API responsible for handling all GameNode requests.
The root docker-compose.yml starts the local dependencies: MySQL, SuperTokens,
Redis, MinIO, Manticore, and the Directus blog CMS. The NestJS server runs on the
host when you use the development command below.
If you are looking to self-host GameNode (server), this is the easiest way to do it.
Set up the root .env before starting. See Installation and
the CMS trial guide for details.
The default docker-compose files do not run migrations automatically. Create and review migrations in development against a disposable database, then commit them with the application changes. Do not generate migrations inside a running production container.
At deployment, the server_migration service in docker-compose.prod.yml runs
the committed migrations once and exits; the API starts only after it succeeds,
so replicas never run migrations concurrently. Its image tag is independent of
the API's. Back up the database before deploying a new migration.
The current Git checkout still needs a versioned initial schema: the historical baseline is ignored by Git. See the fresh installation report for the bootstrap prerequisite and migration workflow.
Use docker-compose.prod.yml as the production definition of the application
services, including Directus. Load that file from Git in Coolify. Production
MySQL is not in that file: it is a Coolify-managed database resource, shared by
GameNode, SuperTokens and Directus with separate databases and users, and backed
up by Coolify to R2. Set the production credentials listed in .env.example.
API and migration containers reference an immutable lamarcke/game-node-server release tag directly
in the production Compose. The production base still needs the deployment
corrections listed in the report.
Production requires a one-time manual step: after creating the managed
MySQL, run docker/mysql/init-databases.sh inside it to create the SuperTokens
and Directus databases/users. See section 6.3 of the
fresh installation report.
Changing environment variables later does not update existing users.
Development and disposable test services use the same MySQL, Redis and Manticore versions as production. The updated images target fresh installations; existing local volumes need an engine-specific upgrade plan. MinIO is a pinned legacy image for local development; production uploads use external S3/R2 storage.
If you are developing locally, you can use this command to spin up your containers and start the server:
$ yarn start:dev:docker # docker compose up --build -d --wait && yarn start:devYou can also attach your editor to the Docker container if it supports remote development (like VS Code, IntelliJ IDEA, etc.).
The provided MySQL instance will hold all data for both GameNode Server itself and from Supertokens.
Bugs and issues related to migrations are not covered by support, and should instead be reported to the TypeORM team.
$ yarnUse the .env.example file as an example for your own .env file.
All parameters are required.
Some parameters are omitted because they are not needed for local development.
Set CMS_SECRET and CMS_ADMIN_PASSWORD to random values in .env before
starting the Docker stack.
The development command starts MySQL, Redis, SuperTokens, MinIO, Manticore, and the CMS locally. Configure managed services only when developing outside that Docker stack.
Hosting your own instance of Supertokens is not required for local development. You can instead use their public instance url:
SUPERTOKENS_CORE_URI=https://try.supertokens.ioAfter setting up your database credentials in .env, run the migrations:
$ yarn migration:runApply committed migrations on first setup and whenever a release includes pending migrations. An empty database requires the versioned baseline described above.
When developing a schema change, first bring a disposable database to the previous release's schema, edit the entities, then generate and review a new migration:
$ yarn typeorm migration:generate ./src/migrations/DescribeChange -d ./data-source.tsTest and commit the migration before deployment. Keep applied migrations unchanged
and keep synchronize set to false.
ALWAYS check your migrations before running them. Typeorm may sometimes drop important tables and columns.
We store user uploads in a S3 compatible Object Storage service.
For local development, we recommend MinIO, which is already provisioned when you use the default docker-compose.yml file.
For production, you are free to use any service, as long as it's S3 API compatible, like Cloudflare R2, or AWS S3.
The MinIO Browser interface is exposed in port 9001 by default, and the API is exposed at 9000.
In the MinIO interface, create a bucket named gamenode-user-uploads. This name is mandatory.
In your bucket settings, visit the 'Anonymous' tab, and add a ruleset to allow anonymous reads in your bucket:

This will make all content in your bucket accessible/downloadable, and
is necessary since the API doesn't provide a 'read' endpoint.
If you were upload a file named my_nice_file.png, it would be available at
http://localhost:9000/gamenode-user-uploads/my_nice-file.png
After starting your MinIO instance, it will be made available at localhost:9001, which you can visit in your browser.
The default username and password is gamenode.
You may create an access token and secret key directly in MinIO interface:

After creating, edit your .env to reflect your new credentials:
S3_ENDPOINT=http://localhost:9000
S3_ACCESS_KEY_ID=RHfLD2qR3MmD2cngzJRE
S3_SECRET_ACCESS_KEY=eRyW1PuHRVRSsqS2RrytugItZNyczDEvTRvhjQJI
GameNode's games metadata are powered by IGDB. To use it, you need to set up game-node-sync-igdb. See CONTRIBUTING for a guide.
The system-wide PSN credential is derived from an NPSSO. PSN_NPSSO_KEY is only
the initial value: once an admin sets an NPSSO, the encrypted one stored in the
database (using CONNECTION_CREDENTIAL_ENCRYPTION_KEY) takes precedence, and editing
the env variable has no effect anymore.
To rotate it without a restart, send PUT /v1/psn/admin/npsso with { "npsso": "..." }
as an admin. The value is validated with Sony before being saved, and the response
only contains refreshTokenExpiresAt.
An hourly check keeps the credential alive. Authentication failures are logged as
errors containing PSN_SYSTEM_AUTH_FAILED, and a warning is logged when the refresh
token expires in less than 7 days. Alert on these logs to know when to rotate.
Achievements are rows in achievement_definition, so designers create them without a deploy.
Give the designer the editor role in SuperTokens; admin works too. Everything below lives
under /v1/achievements/admin.
- Metrics:
GET metricslists what can be counted (reviews_written,library_games,games_finished,followers,comments_written,awards_participation,manual), the filters each accepts (game, theme and genre ids, or the awards year) and whether it supports a time window. A new metric needs code insrc/achievements/metrics/achievement-metrics.ts; a new achievement does not. - Definitions:
POST definitionscreates a draft with a slug (id), a metric, criteria, 1 to 10 tiers (strictly increasing thresholds, 0 to 5000 XP each) and an icon: a kebab-case glyph, an existing game's cover or an existing icon inpublic/icons.POST definitions/:id/simulatewith{ "userId" }shows the metric value and reachable tiers without granting anything. - Editorial cycle: drafts can be edited (
PATCH) and deleted.POST .../publishmakes a definition live; after that, the metric, criteria, window and existing tiers are frozen and only new, higher tiers can be appended.POST .../archivestops evaluation and hides it from the catalog, while users keep the tiers they obtained. - Events:
POST eventscreates a time window (UTC) with the same cycle. Definitions linked to an event (eventId) count only facts inside its window and are public only once both are published. After publishing, the start is frozen and the end can only be extended. - Backfill: publishing a definition, appending a tier or publishing an event grants tiers to
users who already qualify, in batches of 500 on the
achievements-backfill-queue. Retroactive grants give XP but no notification or feed activity.
# development
$ yarn start
# watch mode
$ yarn start:dev
# production mode
$ yarn start:prodThe stateless Streamable HTTP endpoint is ${DOMAIN_API}/v1/mcp (locally,
http://localhost:5000/v1/mcp). It requires an OAuth access token issued for that
resource; website session tokens do not authenticate MCP requests.
Discovery is public at these paths on DOMAIN_API:
/.well-known/oauth-protected-resource/v1/mcp/.well-known/oauth-authorization-server/v1/auth/.well-known/oauth-authorization-server(compatibility alias)
| Scope | Tools |
|---|---|
games:read |
search_games, get_game |
library:read |
get_library_entry, list_collections |
library:write |
add_game_to_library |
reviews:write |
review_game |
posts:write |
create_post |
mappings:write |
list_unmapped_games, map_external_game, dismiss_unmapped_game |
The mappings:write tools work through the queue of platform games GameNode could
not map. They are only listed and callable for users with the ADMIN role, checked on
every request, so holding the scope is not enough. Mappings they write have origin
agent, never overwrite an existing mapping, and can be reviewed or reverted with
origin = 'agent' in game_external_game. Their writes count toward the same limit.
OAuth also supports openid, profile and offline_access; the last enables
rotating refresh tokens. Clients can use CIMD or register dynamically without a
preconfigured client id or secret. Writes share a limit of 10 per user per minute
and stop immediately when the user is suspended or banned. Reviews require
overwrite: true to replace an existing review. Content is plain text, escaped
into HTML paragraphs before storage.
Website prerequisite: game-node-clients must provide
${DOMAIN_WEBSITE}/oauth/consent and make ${DOMAIN_WEBSITE}/auth resume the
pending authorization after sign-in. The consent page must show the client name,
URI, verified CIMD domain and requested scopes, and submit accept/deny through
Better Auth's OAuth client. Discovery works before that companion change ships,
but users cannot finish connecting. Local end-to-end checks need a stub consent
page. Run yarn migration:run before starting the updated API.
For a Claude.ai custom connector,
open Customize → Connectors → + → Add custom connector, name it GameNode
and enter your publicly reachable HTTPS ${DOMAIN_API}/v1/mcp URL. Leave the
OAuth client credentials empty, then connect, sign in and approve the scopes.
Claude.ai connects from Anthropic's cloud, so it cannot reach localhost.
For Claude Code, register the local server:
claude mcp add --transport http gamenode http://localhost:5000/v1/mcpUse the deployed HTTPS URL instead for a remote server. Open /mcp in Claude Code,
select GameNode and authenticate to complete sign-in and consent.
To inspect discovery locally:
curl http://localhost:5000/.well-known/oauth-protected-resource/v1/mcp
curl http://localhost:5000/.well-known/oauth-authorization-server/v1/auth# TypeScript checks for source and tests
$ yarn test:typecheck
# unit tests
$ yarn test
# e2e tests
$ yarn test:e2e
# test coverage
$ yarn test:covJest compiles TypeScript files individually, uses at most two workers, and recycles
workers whose heap exceeds 512 MB after a suite. This is not a hard process memory
limit, and recycling does not apply to --runInBand.
Run yarn test:typecheck for full type checking; CI runs it
before Jest. Use yarn test --maxWorkers=N to override the worker limit.
Keep in mind that it's not possible to test Supertokens' routes (nor should we have to).
Feel free to reach out to us through a Github Issue. We can help you set up your own instance of GameNode, or help you with any other issue you might have.
PS: If you have issues with overral app usage, please fill an issue in the corresponding repository.
For example, if you have issues with the GameNode website, fill an issue in the game-node-web repository.