Initialize a new user

It's often useful when a new user registers to have a bunch of records setup for them. In games this could be needed for a user's virtual wallet, initial inventory items, etc. In this tutorial we'll cover a few different ways to handle this use case.

Summary

While there are various ways to solve this use case we highly recommend you initialize the records on usage.

After register callback

The simplest approach is to write records in the success callback for the register function in a client.

This code demonstrates how to do it with a condensed example. In real application code you'll break up the authentication and connect logic from the storage writes based on how you manage connect and reconnect.

var deviceid = SystemInfo.deviceUniqueIdentifier;
var session = await client.AuthenticateDeviceAsync($"{deviceid}");

var json = "{\"coins\": 100, \"gems\": 10, \"artifacts\": 0}";
var object = new WriteStorageObject = {
  "collection" = "wallets",
  "key" = "mywallet",
  "value = json
};

const storageWriteAck = await client.WriteStorageObjectsAsync(session, objects);
Debug.Log("Successfully setup new user's records.");

This code has tradeoffs which should be noted. A disconnect can happen before the records are written to storage. This may leave the setup of the user incomplete and the application in a bad state.

This option is only worth choosing when you want to avoid writing server-side code or have built retry logic on top of a client.

Server-side hook

Another way to write records for the new user is to run server-side code after registration has completed. This can be done with a register hook.

The "register_after" hook can be used with one of the "authenticaterequest_*" message types to tell the server to run a function after that message has been processed. It's important to note that the server does not distinguish between register and login messages so we use a conditional write to store the records.

local nk = require("nakama")

local function initialize_user(context, _payload)
  local value = {
    coins = 100,
    gems = 10,
    artifacts = 0
  }
  local record = {
    collection = "wallets",
    record = "mywallet",
    user_id = context.user_id,
    value = value,
    version = "*"   -- only write record if one doesn't already exist.
  }
  pcall(nk.storage_write, { record }) -- write record, ignore errors.
end

-- change to whatever message name matches your authentication type.
nk.register_req_after(initialize_user, "authenticaterequest_device")

This approach avoids the tradeoff with client disconnects but requires a database write to happen after every login or register message. This could be acceptable depending on how frequently you write data to the storage engine and can be minimized if you cache a user's session for quick reconnects.