Introduction #
Nakama includes a fast embedded code runtime enabling you to write custom logic as a JavaScript bundle, Go plugins, and Lua modules.
The runtime framework is essential to writing server-side logic for your games or apps. Use it to write code you would not want to run on client devices or the browser. The code you deploy with the server can be used immediately by clients, allowing you to change behavior on the fly and add new features faster. This code can be used to run authoritative logic or perform validation checks as well as integrate with other services over HTTPS.
Use server-side code when you want to set rules around various features, like how many friends a user may have or how many groups they can join.
This page will cover the key concepts and functionality available in the Nakama runtime framework.
Loading modules #
By default the server will scan all files within the data/modules
folder relative to the server file or the folder specified in the YAML configuration at startup. You can also specify the modules folder via a command flag when you start the server.
Files with the .lua
, .so
, and .js
extensions found in the runtime path folder will be loaded and evaluated as part of the startup sequence. Each of the runtimes has access to the Nakama API to operate on messages from clients as well as execute logic on demand.
The different supported languages are loaded with a precedence order of Go -> Lua -> JavaScript. This ensures deterministic behavior if match handlers or RPC functions/hooks are registered in multiple runtimes, providing the flexibility to leverage the different runtimes as best suited and have them work seamlessly together. For example, you can define an RPC function in the JavaScript runtime to create a match with a set of match handlers written in Go.
JavaScript runtime #
The JavaScript runtime expects an index.js
file. To change the name of the relative file path where the code will be loaded within the runtime path you can set it in the server YML or as a command flag.
|
|
This path must be relative to the default or set runtime path.
Go runtime #
The Go runtime looks for a Go plugin .so
shared object file.
To learn how you can generate this file with your custom Go runtime code follow see building the Go shared object.
Lua runtime #
The Lua runtime will interpret and load any .lua
files, including those in a subdirectory. These can be referenced as modules with relative paths.
Each Lua file represents a module and all code in each module will be run and can be used to register functions.
Database handler #
The runtime includes a database object that can be used to access the underlying game database. This enables you to include custom SQL queries as part of your game design and logic.
The database handler has a limit on the number of available connections to the database. This can lead to slowed response time for your users along with other errors. To avoid such issues you must ensure that your custom SQL queries properly release the connection once finished with the relevant row(s).
If using db.QueryContext()
or db.Query()
, you must call row.Close()
after you are finished with the database rows data.
If using db.QueryRow()
or db.QueryRowContext()
, you must call either row.Scan
or row.Close()
after you are finished with the database rows data.
Logger #
A logger instance included in the server runtime enables you to write and access log messages in your server code using the following severities: ERROR
, WARN
, INFO
, and DEBUG
.
INFO
and DEBUG
levels are disabled in production environments due to the verbosity of their outputs. They are still available in Dev
environments.See an example for the TypeScript, Go, and Lua runtime.
Nakama module #
The Nakama module is included in the code runtime built into the server. This module provides access to a range of functions for implementing custom logic and behavior.
See the function reference for your preferred language to learn about the available functions:
Functionality #
RPC functions #
Remote Procedure Calls (RPCs) let you call functions registered in your runtime code to operate on messages received from clients or execute custom logic on demand.
These functions are exposed via RESTful HTTP endpoints, the realtime socket APIs, and gRPC. They can be invoked using any of these means directly, or through our client SDKs.
- Create the RPC logic:
|
|
|
|
|
|
- Register your new RPC:
|
|
|
|
|
|
- The registered RPC Function can be invoked with any HTTP client of your choice. Your new endpoint will look like this:
http://127.0.0.1:7350/v2/rpc/authoritative_write_rpc
Hooks #
You can register functions in your runtime code to be called when certain events occur. Learn more about Hooks and their uses.
Server to server #
You can check if the context has a user ID to see if an RPC function is a client or server-to-server call. Server to server calls will never have a user ID. If you want to scope functions to never be accessible from the client just return an error if you find a user ID in the context.
See the server runtime examples.
Run once #
The runtime environment allows you to run code that must only be executed only once. This is useful if you have custom SQL queries that you need to perform or to register with third party services.
See the implementation examples for TypeScript, Go, or Lua for an example.
Restrictions #
See the TypeScript, Go, and Lua pages for runtime specific restrictions.
Background jobs #
To avoid “dead work” - done when the user is not present - and the unnecessary server load, background jobs should be avoided in favor of an event driven route. In this approach, the client makes an RPC call when the user returns, and in that called function any updates required by your game logic and use case are performed.
Where a scheduled background job would needlessly perform these updates across the entire user base, regardless of a user’s inactivity, this approach ensures that work is only performed for users still active in the game.
Use of background jobs can cause further problems as any job would be limited to one Nakama instance, requiring either duplication across all instances or a non-homogenous workload across instances.