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 initThis 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:
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:
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 testDevelopment Console
For rapid development and debugging, you can use the interactive development console:
npx -y @agent8/gameserver-node devIt 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)
100The 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:
@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/deployThe 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
- Initialize:
npx -y @agent8/gameserver-node init - Develop: Write server logic in
server/src/ - Test interactively:
npx -y @agent8/gameserver-node dev(optional) - Test:
npx -y @agent8/gameserver-node test— this builds first, so you do not need a separate build step - 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
- Run
npx -y @agent8/gameserver-node init. - Move the class from
server.jsintoserver/src/server.ts, addexport, and add type annotations. The server API does not change, so neither does client code. - 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')becomesremoteFunction('game.hello')— so adopt them only if you want namespaces. - Write tests in
server/test/server.test.tsand runnpx -y @agent8/gameserver-node testuntil 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.