Type-safe end-to-end
Every RPC call, state mutation, event payload, and config field is typed. Zero string-based events. Zod validation on every external input.
TypeScript-first. Type-safe. PLA-compliant. Built for modern RP servers.
Build a banking module in three files:
// modules/banking/src/server/service.ts
export class BankingService {
private balances = new Map<number, { cash: number; bank: number }>()
async transfer(from: number, to: number, amount: number) {
const a = this.balances.get(from) ?? { cash: 0, bank: 0 }
if (a.cash < amount) throw new Error('INSUFFICIENT_FUNDS')
a.cash -= amount
const b = this.balances.get(to) ?? { cash: 0, bank: 0 }
b.cash += amount
this.balances.set(from, a)
this.balances.set(to, b)
}
}// modules/banking/src/server/router.ts
import { defineRouter, procedure, RpcError, z } from '@nextvm/core'
export function buildBankingRouter(service: BankingService) {
return defineRouter({
transfer: procedure
.input(z.object({ to: z.number(), amount: z.number().positive() }))
.mutation(async ({ input, ctx }) => {
if (!ctx.charId) throw new RpcError('NOT_FOUND', 'No active character')
await service.transfer(ctx.charId, input.to, input.amount)
return { ok: true }
}),
})
}// modules/banking/src/index.ts
import { defineExports, defineModule, z } from '@nextvm/core'
import { buildBankingRouter } from './server/router'
import { BankingService } from './server/service'
export type BankingExports = ReturnType<typeof buildExports>
const buildExports = (s: BankingService) => defineExports({
service: s,
transfer: s.transfer.bind(s),
})
export default defineModule({
name: 'banking',
version: '0.1.0',
config: z.object({}),
server: (ctx) => {
const service = new BankingService()
const router = buildBankingRouter(service)
ctx.setExports(buildExports(service))
ctx.log.info('banking ready', { procedures: Object.keys(router).length })
},
client: () => {},
})Then nextvm build and you're done. The framework generates fxmanifest.lua, bundles your locales, splits server + client, validates your config schema, and produces a FXServer-ready resource under dist/.
| Concern | ESX/QBCore | NextVM |
|---|---|---|
| Type safety | None | Full compile-time |
| Event registration | Manual RegisterNetEvent | Generated by defineRouter |
| Input validation | Manual or skipped | Automatic via Zod |
| Rate limiting | Manual or none | Built-in per-player |
fxmanifest.lua | Hand-written | Auto-generated |
| Config validation | None | Zod at startup |
| NUI communication | Manual message passing | Typed ctx.ui.send() |
| Localization | Manual Lua tables | Built-in i18n with t() |