Skip to Content
Game ServerStructured Server Project

Structured Project

The default server layout, and the one Getting Started sets up: your server lives in server/src/ as TypeScript, with a test runner, a development console, and room to split the code across files. This page is its reference. The older single-file server.js format still deploys, and migrating from it is covered below.

Initialization

Run init before you write any files by hand — it generates the layout, package.json and the test harness that test and dev expect. If the server/ directory doesn’t exist in your project, run:

npx -y @agent8/gameserver-node init

This command automatically generates the following project structure:

      • server.ts
      • server.test.ts
    • README.md
    • package.json
    • tsconfig.json

After initialization, you can modify the generated files to implement your game logic. The workflow is to initialize first, then continue with development.

Basic Server Implementation

The server/src/server.ts file contains your server logic as an exported class:

server/src/server.ts
export class Server { async ping(): Promise<string> { return 'pong'; } async getMyAccount(): Promise<string> { return $sender.account; } async updateScore(points: number): Promise<number> { const myState = await $global.getMyState(); const newScore = (myState.score || 0) + points; await $global.updateMyState({ score: newScore }); return newScore; } }

The code is functionally identical to server.js, but uses TypeScript and requires an export statement.

Testing

Writing Tests

Tests are defined in server/test/server.test.ts:

server/test/server.test.ts
describe('Server', () => { test('ping returns pong', async (server) => { const result = await server.ping(); expect(result).toBe('pong'); }); test('getMyAccount returns account', async (server) => { const result = await server.getMyAccount(); expect(result).toBeTruthy(); }); test('connect changes user', async (server) => { // Change to different user server.connect({ account: 'user-alice' }); const account = await server.getMyAccount(); expect(account).toBe('user-alice'); }); });

The server parameter provides:

  • server.connect({ account: 'user-id' }) - Switch to a different user context
  • Direct access to all server functions for testing

Running Tests

Run tests from your project directory (not from server/):

npx -y @agent8/gameserver-node test

Development Console

For rapid development and debugging, you can use the interactive development console:

npx -y @agent8/gameserver-node dev

It starts an interactive console where you call your server functions directly and see the results — handy for trying a function before writing a test for it:

> ping() 'pong' > getMyAccount() 'user-123' > updateScore(100) 100

The dev console provides the same environment as your tests, allowing you to switch users with server.connect({ account: 'user-id' }) and call any server function interactively.

Handler Namespaces

For better code organization, you can separate functionality into different handlers using the @Handler decorator:

server/src/userHandler.ts
@Handler('user') class UserHandler { async getMyAccount(): Promise<string> { return $sender.account; } }
⚠️

Handler files must sit directly in server/src/. The build auto-imports every .ts file in that directory (other than server.ts) so the decorators run, but it does not recurse — a handler in server/src/handlers/userHandler.ts is never imported, its @Handler never executes, and the namespace is silently missing at runtime with no build error. Keep handlers flat, or import the nested file explicitly from server.ts.

Accessing Namespaced Functions

In tests:

const account = await server.user.getMyAccount();

From client:

const result = await server.remoteFunction('user.getMyAccount');

Building and Deployment

On the Agent8 platform, building and deployment are automatic. Push your code and the platform builds server/src/ into server/dist/server.js and deploys it — no commands to run.

Hosting the project yourself, build and then deploy the output:

npx -y @agent8/gameserver-node build npx -y @agent8/deploy

The deploy tool uploads server/dist/server.js but does not build it, so run build (or test, which builds first) before every deploy.

⚠️

Deployment priority: if a root ./server.js exists it is deployed instead of server/dist/server.js.

Important Limitations

Isolated VM Environment

Your server code runs in an isolated-vm environment for security and performance. This comes with certain limitations:

✅ Available

  • Provided contexts: $sender, $global, $room, $asset, $lock — reference: Server Globals
  • Pure JavaScript/TypeScript libraries (lodash, date-fns, etc.)
  • Most computation and logic processing

❌ Not Available

  • Node.js built-in modules: fs, http, https, net, child_process, etc.
  • Network request libraries: axios, node-fetch, etc. (with some exceptions)
  • File system access
  • External process execution

The other runtime rules — a fresh Server instance per request, no timers — are on Server Globals.

Workflow Summary

  1. Initialize: npx -y @agent8/gameserver-node init
  2. Develop: Write server logic in server/src/
  3. Test interactively: npx -y @agent8/gameserver-node dev (optional)
  4. Test: npx -y @agent8/gameserver-node test — this builds first, so you do not need a separate build step
  5. Deploy: push to the repository on the platform, or build and deploy yourself

npx -y @agent8/gameserver-node build exists if you want to produce server/dist/server.js on its own, but test and dev already run it.

Migrating from Legacy server.js

  1. Run npx -y @agent8/gameserver-node init.
  2. Move the class from server.js into server/src/server.ts, add export, and add type annotations. The server API does not change, so neither does client code.
  3. Split a large file as you like: helpers can live in any module you import. Handler namespaces (@Handler, above) are optional and do change client call names — remoteFunction('hello') becomes remoteFunction('game.hello') — so adopt them only if you want namespaces.
  4. Write tests in server/test/server.test.ts and run npx -y @agent8/gameserver-node test until they pass, then push (or build and deploy yourself).

You do not need to delete the old server.js: the first successful build copies it to .server.old.js (with a deprecation banner) and removes the original, printing 📦 Renamed legacy server.js → .server.old.js, so it no longer takes deployment priority. Keep .server.old.js for reference or delete it once you are satisfied.

Last updated on