Structured Project
For larger server projects that require testing, better organization, and scalability, we provide a structured TypeScript-based approach. This method maintains backward compatibility with the simple server.js approach while offering enhanced development capabilities.
Initialization
IMPORTANT: Always initialize your server project with this command first.
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
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 Goal: Always write both server code AND tests. After running tests, check the terminal output to see if they pass or fail, then iterate and fix any issues until all tests pass.
Development Console
For rapid development and debugging, you can use the interactive development console:
npx -y @agent8/gameserver-node devThis command starts an interactive console where you can directly call your server functions and see the results in real-time. It’s useful for:
- Testing individual functions without writing formal tests
- Quick debugging and experimentation
- Exploring your API during development
Example usage:
> ping()
'pong'
> getMyAccount()
'user-123'
> updateScore(100)
100The dev console provides the same environment as your tests, allowing you to switch users with 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;
}
}Accessing Namespaced Functions
In tests:
const account = await server.user.getMyAccount();From client:
const result = await server.remoteFunction('user.getMyAccount');Beyond the handler namespace system, you have complete freedom to architect your server code however you prefer. You can organize classes, modules, and utilities in any way that suits your project.
Building and Deployment
Building and deployment are automatically handled by the Agent8 platform. You don’t need to manually run build or deploy commands.
When you push your code to the repository or use the platform’s deployment features:
- The platform automatically builds your TypeScript server from
server/src/toserver/dist/server.js - The platform automatically deploys the built server
Deployment Priority:
- If
./server.jsexists in the project root, it takes priority - Otherwise,
server/dist/server.jsis deployed
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 - 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 isolated-vm environment ensures consistent performance and security across all game servers, but requires pure JavaScript/TypeScript code without Node.js-specific features.
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 - Build:
npx -y @agent8/gameserver-node build(server/dist/server.js) - Deploy: Push to repository - building and deployment are handled automatically by the platform
Migrating from Legacy server.js
If you have an existing server.js file, you can migrate to the Structured Project approach:
Migration Steps
- Initialize the structured project:
npx -y @agent8/gameserver-node init-
Convert your code:
- Move code from
server.jstoserver/src/server.ts - Add
exportto your Server class - Add TypeScript type annotations
- Move code from
-
File Organization:
- If your
server.jsis large (>200 lines), consider splitting into multiple files - You can organize code in several ways:
Option A: Keep everything in Server class (no client changes needed)
server/src/ server.ts (main server class with all functions) utils/ calculations.ts (helper functions - not exposed to clients)- Client code remains the same:
remoteFunction('hello')
Option B: Use handler namespaces with
@Handlerdecorator (requires client updates)server/src/ server.ts (main server class) gameHandler.ts (@Handler('game') for game logic) userHandler.ts (@Handler('user') for user management) utils/ calculations.ts (helper functions)- Important: Using handlers requires updating client code
- Example:
remoteFunction('hello')becomesremoteFunction('game.hello') - Choose this only if you want to organize functions into namespaces
- If your
Migration Example
Before (server.js):
class Server {
async updateScore(points) {
const myState = await $global.getMyState();
const newScore = (myState.score || 0) + points;
await $global.updateMyState({ score: newScore });
return newScore;
}
}After (server/src/server.ts):
export class Server {
async updateScore(points: number): Promise<number> {
const myState = await $global.getMyState();
const newScore = (myState.score || 0) + points;
await $global.updateMyState({ score: newScore });
return newScore;
}
}Key Changes:
- Add
exportkeyword - Add type annotations for parameters (
points: number) - Add return type (
Promise<number>) - For large files, you can split into multiple files or use utility modules
About @Handler Decorator:
Using @Handler for namespaces is optional. If you use it:
- Client code must be updated:
remoteFunction('hello')→remoteFunction('game.hello') - Choose this only if you want organized namespaces
- If you want to avoid client changes, keep all functions in the main
Serverclass
-
Write Tests:
- Create tests in
server/test/server.test.ts - Test all migrated functions
- Run
npx -y @agent8/gameserver-node testto verify
- Create tests in
-
Deploy:
- Once tests pass, push to repository
- The platform will automatically build and deploy
After successful migration and deployment, you can delete the old server.js file from the project root.