> ## Documentation Index
> Fetch the complete documentation index at: https://v5.rpgjs.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Node server in production

> Run RPGJS without Vite in production, secure map updates, and mount the transport in a Node HTTP server.

# Node server in production

`@rpgjs/server/node` lets you run the RPGJS server runtime without Vite.

If you want to structure an MMORPG project with a framework-agnostic `src/server.ts` plus host-specific entries such as Express, read [/advanced/mmorpg-entries](/advanced/mmorpg-entries) first.

Use it when you want to mount the server in your own Node stack:

* Express
* Fastify
* Hono on Node
* a custom `http.createServer()` setup

## Dev vs production

In development with `@rpgjs/vite`, map rooms are initialized automatically by the Vite plugin.

In production, map updates must come from a trusted backend source. To protect `/map/update`, set `RPGJS_MAP_UPDATE_TOKEN`.

When this environment variable is set:

* gameplay clients cannot update maps
* trusted backend code must send the token
* you can use `transport.updateMap()` or call the HTTP endpoint yourself

## 1. Create the transport

```ts theme={null}
import http from "node:http"
import express from "express"
import { WebSocketServer } from "ws"
import { createRpgServerTransport } from "@rpgjs/server/node"
import ServerModule from "./server"

const app = express()
const server = http.createServer(app)
const wsServer = new WebSocketServer({ noServer: true })

const transport = createRpgServerTransport(ServerModule, {
  initializeMaps: false,
  mapUpdateToken: process.env.RPGJS_MAP_UPDATE_TOKEN,
})

app.use("/parties", (req, res, next) => {
  void transport.handleNodeRequest(req, res, next, {
    mountedPath: "/parties",
  })
})

server.on("upgrade", (request, socket, head) => {
  void transport.handleUpgrade(wsServer, request, socket, head)
})

server.listen(3000)
```

## 2. Push trusted map updates

If your map data is produced inside the same trusted Node process, use `transport.updateMap()`.

```ts theme={null}
await transport.updateMap("town", {
  id: "town",
  width: 3200,
  height: 2400,
  events: [],
  data: tiledXml,
})
```

`transport.updateMap("town", ...)` targets the room `map-town` automatically.

## 3. Call `/map/update` from another trusted backend

If your editor pipeline, admin API, or deploy step runs outside the game server process, call the endpoint directly with the token.

Endpoint format:

```txt theme={null}
POST /parties/main/map-<mapId>/map/update
```

Example:

```ts theme={null}
import { createMapUpdateHeaders } from "@rpgjs/server/node"

await fetch("http://localhost:3000/parties/main/map-town/map/update", {
  method: "POST",
  headers: createMapUpdateHeaders(process.env.RPGJS_MAP_UPDATE_TOKEN),
  body: JSON.stringify({
    id: "town",
    width: 3200,
    height: 2400,
    events: [],
    data: tiledXml,
  }),
})
```

You can also send the token as:

* `x-rpgjs-map-update-token: <token>`
* `Authorization: Bearer <token>`

## Recommended production flow

1. Start your Node server with `RPGJS_MAP_UPDATE_TOKEN` set.
2. Mount `transport.handleNodeRequest()` and `transport.handleUpgrade()`.
3. Keep `initializeMaps: false` in production.
4. From a trusted backend source, call `transport.updateMap()` or `POST /parties/main/map-<id>/map/update`.
5. Let gameplay clients use only normal movement and game actions.

## WebSocket session ids

The Node transport is backed by `@signe/room/node` and follows the
`@signe/room` session model:

* `conn.id` is unique for each active WebSocket connection.
* `conn.sessionId` is the stable private session id sent by the client.

`provideMmorpg()` sends this stable id through PartySocket, so a browser refresh
or a second tab can restore the same player session without replacing the first
active WebSocket. When you need to address or exclude a single physical socket,
use `conn.id`; when you need to inspect the restored player session, use
`conn.sessionId`.

## Hono and other runtimes

The same transport can be used outside Express:

* use `transport.fetch()` when your framework exposes Fetch `Request`/`Response`
* use `transport.handleNodeRequest()` when your framework gives Node `req`/`res`
* use `transport.acceptWebSocket()` or `transport.handleUpgrade()` for WebSocket upgrades

## Security note

Do not expose `/map/update` without a token in public MMORPG production deployments.

`/map/update` is a trusted server-side operation. It can redefine map geometry, events, and world metadata.

## Development with Vite

With `@rpgjs/vite`, you do not need this manual flow for local development.

The Vite plugin creates the transport internally and performs the server-side map bootstrap automatically.
