End-to-End Quickstart
A 10-minute walkthrough that takes a fresh machine and ends with a NextVM-built FiveM resource serving real RPC calls from a real client.
Prerequisites
- Node.js 22+, pnpm 9+
- A running FXServer (
txAdminor manual) - MySQL 8 (optional — the runtime falls back to in-memory)
1. Scaffold a project
pnpm dlx @nextvm/cli create my-server
cd my-server
pnpm installThis creates:
my-server/
├── nextvm.config.ts
├── modules/
│ └── core/ # generated example module
├── package.json
└── tsconfig.json2. Add a feature module
nextvm add shop --full--full scaffolds a complete module skeleton with state, router, locales, and a placeholder service. Open modules/shop/src/index.ts and you'll see:
import { defineModule, z } from '@nextvm/core'
import { buildShopRouter } from './router'
import { ShopService } from './service'
export default defineModule({
name: 'shop',
version: '0.1.0',
config: z.object({
startingCredit: z.number().int().default(100),
}),
server: (ctx) => {
const service = new ShopService()
const router = buildShopRouter(service)
ctx.exposeRouter(router)
ctx.setExports({ service })
},
})3. Wire the server bootstrap
Create modules/core/src/server/index.ts:
import { bootstrapServer } from '@nextvm/runtime-server'
import { Database, MySqlAdapter, DbCharacterRepository } from '@nextvm/db'
import shop from '../../../shop/src'
const db = new Database(new MySqlAdapter({
host: GetConvar('mysql_host', 'localhost'),
user: GetConvar('mysql_user', 'root'),
password: GetConvar('mysql_password', ''),
database: GetConvar('mysql_db', 'nextvm'),
}))
await bootstrapServer({
modules: [shop],
characterRepository: new DbCharacterRepository(db),
})Without a database? Drop the characterRepository line — the runtime falls back to an in-memory repository so you can smoke-test without touching MySQL.
4. Wire the client bootstrap
Create modules/core/src/client/index.ts:
import { bootstrapClient } from '@nextvm/runtime-client'
import { createClient } from '@nextvm/core'
import shop from '../../../shop/src'
import type { buildShopRouter } from '../../../shop/src/router'
const runtime = await bootstrapClient({ modules: [shop] })
// Typed RPC client — every call goes server-side, validated by Zod.
const shopRpc = createClient<ReturnType<typeof buildShopRouter>>(
'shop',
runtime.transport.call,
)
const offers = await shopRpc.listOffers()
console.log(offers)5. Validate
nextvm validateRuns all 13 architecture guards. If you imported a FiveM native directly, used TriggerServerEvent, mutated global state, or built a non-PLA-compliant payment flow, this fails before you ever hit production.
6. Build
nextvm buildFor each module, this:
- Compiles TypeScript with tsup → ESM
- Generates
fxmanifest.lua - Bundles
src/shared/locales/*.ts→dist/locales/*.json
Result:
modules/shop/
├── dist/
│ ├── server.js
│ ├── client.js
│ └── locales/{en,de}.json
└── fxmanifest.lua7. Deploy
Copy each modules/<name>/ folder into your FXServer's resources/ directory. The folder is self-contained — dist/, fxmanifest.lua, and any locale assets travel together.
In server.cfg:
ensure shop
ensure corecore (the bootstrap resource) must come last so its dependencies are already up.
8. Verify
Connect a client. The server log shows:
[nextvm:loader] Initializing modules { side: 'server', order: ['shop', 'core'], count: 2 }
[nextvm:loader] Module initialized { module: 'shop', side: 'server' }
[nextvm:loader] Module initialized { module: 'core', side: 'server' }
[nextvm:loader] All modules ready { count: 2, side: 'server' }
[nextvm:runtime] Registered RPC router { module: 'shop' }
[nextvm:runtime] FiveM event bridge attached
[nextvm:runtime] NextVM server runtime ready { modules: 2 }When the player joins:
[nextvm:runtime] handlePlayerConnecting { source: 5, name: 'Tom' }
[shop] new character ready { charId: 1 }The first RPC call from the client (e.g. shopRpc.listOffers()) hits the server's __nextvm:rpc net event, which dispatches to shopRouter.listOffers, runs Zod validation, calls the handler, and emits __nextvm:rpc:response back. The client's promise resolves with the typed result.
9. Iterate with dev
nextvm dev --serveSpawns a local FXServer subprocess against your built modules, streams its logs into your terminal, watches every module's src/, and runs ensure <module> inside FXServer on each successful rebuild — connected players keep their state across the reload via the snapshot mechanism.
If you'd rather manage FXServer externally (txAdmin, Pterodactyl, a remote box), drop the --serve flag — nextvm dev then runs as a pure rebuild watcher and you handle ensure yourself.
See the Local FXServer guide for the full setup, including how to wire a cfx-server-data install and a fxserver block in nextvm.config.ts.
What just happened
You wrote a TypeScript module, validated it against 13 architecture guards, built it to a self-contained FiveM resource, and connected both client and server runtimes through a typed RPC layer — without touching a single FiveM native, TriggerServerEvent, or global state.
That's the entire NextVM loop.
See also
- Module Authoring — deeper dive on conventions
- Architecture Overview — the 5-layer model
- Testing — how to unit-test modules in plain Node
@nextvm/runtime-server— bootstrap reference@nextvm/runtime-client— bootstrap reference