This document describes the HTTP and WebSocket API exposed by the wow-backend service and how authors can interact with it (both server-side and from the browser client).
Summary
- HTTP base:
http://<host>:3000(server listens on port 3000 by default). - WebSocket events endpoint:
ws://<host>:3000/events. - Frontend client uses
openapi-fetch(seepackages/wow-frontend/src/api/client.ts).
HTTP Endpoints
GET /wow/world- Returns the current
Worldobject (see@wow/specfor shape). - Example (frontend):
await fetchWorld()frompackages/wow-frontend/src/api/world.ts. - cURL:
- Returns the current
curl http://localhost:3000/wow/worldGET /wow/scene/node/{nodeId}- Returns a single
Nodeby id. - Example (frontend):
await fetchNode(nodeId)frompackages/wow-frontend/src/api/node.ts. - cURL:
- Returns a single
curl http://localhost:3000/wow/scene/node/0POST /wow/scene/node/{nodeId}- Creates one or more child nodes under the given parent node id.
- Request body: array of
Nodeobjects (withoutid— server assigns ids). - Response: created nodes (with assigned
idandparent). - Example (frontend):
createNode(parentId, node)calls this endpoint. - cURL:
curl -X POST http://localhost:3000/wow/scene/node/0 \
-H "Content-Type: application/json" \
-d '[{"label":"Lamp","children":[],"spatialAssetURI":"https://example.com/assets/lamp.glb"}]'PUT /wow/scene/node/{nodeId}- Updates an existing node. Body should contain partial or full
Nodeproperties to be merged. - Response: updated
Node. - cURL:
- Updates an existing node. Body should contain partial or full
curl -X PUT http://localhost:3000/wow/scene/node/1 \
-H "Content-Type: application/json" \
-d '{"label":"Renamed Lamp"}'DELETE /wow/scene/node/{nodeId}- Deletes the given node and its subtree (cannot delete root id
0). - cURL:
- Deletes the given node and its subtree (cannot delete root id
curl -X DELETE http://localhost:3000/wow/scene/node/1GET /wow/scene/portal/{portalId}- Returns portal metadata.
- cURL:
curl http://localhost:3000/wow/portal/1GET /wow/view/{viewId}- Returns a
Viewobject. - cURL:
- Returns a
curl http://localhost:3000/wow/view/1GET /wow/user/{userId}- Retrieve a user record.
- cURL:
curl http://localhost:3000/wow/user/1DELETE /wow/user/{userId}- Delete a user record.
- cURL:
curl -X DELETE http://localhost:3000/wow/user/1- Root web app:
GET /serves a minimal HTML app (seepackages/wow-backend/src/controllers/app.ts).- cURL:
curl http://localhost:3000/Schema examples
World
{
"content": {
"label": "OpenSpatialWorld",
"age_restriction": 0,
"license": "CC-BY-4.0",
"cost": "free",
"version": 1,
"duration": 3600
},
"geoPose": {
"position": { "lat": 47.6, "lan": -122.3, "h": 10 },
"angles": { "yaw": 0, "pitch": 0, "roll": 0 }
},
"presence": {
"avatar": "headset",
"navigation": "teleport",
"gravity": "earth"
},
"technology": {
"webXR-immersive": "required",
"runtime": "WebGL2"
},
"users": {
"active_user_count": 1,
"total_user_count": 1
},
"views": {
"view_count": 3
},
"portals": {
"portal_count": 2
}
}User
{
"id": 1,
"name": "Guest-1",
"AvatarURI": "https://example.com/avatar.glb",
"geoPose": {
"position": { "lat": 0, "lan": 0, "h": 1.8 },
"angles": { "yaw": 180, "pitch": 0, "roll": 0 }
}
}View
{
"id": 5,
"geoPose": {
"position": { "lat": 47.6, "lan": -122.3, "h": 5 },
"angles": { "yaw": 90, "pitch": 0, "roll": 0 }
}
}Portal
{
"id": 2,
"geoPose": {
"position": { "lat": 47.6, "lan": -122.3, "h": 0 },
"angles": { "yaw": 270, "pitch": 0, "roll": 0 }
}
}Node
{
"id": 10,
"label": "Tree",
"names": ["Oak", "ForestObject"],
"parent": 0,
"children": [11, 12],
"localTransform": [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1],
"spatialAssetURI": "https://example.com/assets/tree.glb",
"appearanceURI": "https://example.com/appearance/tree.mat"
}POST /wow/scene/node/{nodeId}request body example
[
{
"label": "Lamp",
"children": [],
"localTransform": [1,0,0,0,0,1,0,0,0,0,1,0,0,2,0,1],
"spatialAssetURI": "https://example.com/assets/lamp.glb",
"appearanceURI": "https://example.com/appearance/lamp.mat"
}
]WebSocket Events (/events)
-
The server accepts WebSocket connections at
/eventsand registers each socket. -
On connect, the server creates a transient
User(Guest-X) and broadcasts auser_joinedmessage. -
On disconnect, the server broadcasts a
user_leftmessage. -
Server also broadcasts node events when nodes are created/updated/deleted:
node_created— payload:{ type: 'node_created', node: <Node> }node_updated— payload:{ type: 'node_updated', node: <Node> }node_deleted— payload:{ type: 'node_deleted', nodeId: <number> }
-
The backend sends JSON messages by calling
broadcast('/events', message)(seepackages/wow-backend/src/services/websocket.ts).
Browser-side helpers
-
API client:
packages/wow-frontend/src/api/client.tscreates anopenapi-fetchclient withbaseUrl: '/'.- Usage examples in
packages/wow-frontend/src/api/node.tsandworld.tsshow how to callGET,POST,PUT,DELETEwith path params and body.
- Usage examples in
-
3D viewer:
packages/wow-frontend/src/scene.tsrenders the resolved node tree as an X3DOM scene. EachNode.localTransformbecomes an X3D<MatrixTransform>;Node.spatialAssetURIbecomes an<Inline>reference to that asset (glTF or X3D), and nodes without an asset URI render as a placeholder sphere. -
Events: use
connectEvents(onMessage, onStatusChange)inpackages/wow-frontend/src/events.ts.- Example:
import { connectEvents } from './events';
connectEvents(
(msg) => console.log('event:', msg),
(online) => console.log('events online:', online)
);Author Examples
- Fetch world from browser:
import { fetchWorld } from './api/world';
const world = await fetchWorld();
console.log(world);- Create a node under parent
5:
import { createNode } from './api/node';
await createNode(5, { label: 'My Object', children: [] });- Update a node:
import { client } from './api/client';
const { data } = await client.PUT('/wow/scene/node/{nodeId}', {
params: { path: { nodeId: 10 } },
body: { label: 'Renamed' },
});- Listen for node events:
connectEvents((msg) => {
const parsed = JSON.parse(msg);
if (parsed.type === 'node_created') {
// use parsed.node
}
}, (online) => console.log('ws online', online));Data persistence & startup
- The server persists world state to
packages/wow-backend/data/world_state.jsonusingstore()andrestore()inpackages/wow-backend/src/services/store.ts. - On startup the server restores state (if available) and seeds a root node with id
0.
Quickstart
- Install dependencies from the repository root:
npm install- Build the workspace packages:
npm run build- Start the backend server:
npm run start:backend- Open the frontend in your browser:
- Visit
http://localhost:3000
- Development mode (optional):
npm run devThis will run nodemon from the repo root, rebuilding on source changes and restarting the backend automatically.
Notes and tips for authors
- Refer to the OpenAPI spec package
@wow/spec(README, schema atpackages/wow-spec/src/schema.yaml) for concrete schema definitions and types. The frontend client is typed against that spec, and the backend validates every request/response against it viaexpress-openapi-validator. - Use the provided client helpers in
packages/wow-frontend/src/apito avoid hand-constructing requests. - WebSocket messages are raw JSON strings; parse them and switch on
type. - The server auto-assigns numeric ids for nodes and users.
Relevant files