Using Account username as a friend-code

Hi!

For our game we want users to add each other as friends, and we also want to avoid any user-generated content which includes setting their own username which others can see (we want to avoid the requirement to profanity filter or police this content as our game is available to all ages)

We have decided then to use a friend-code approach where two players can share their friend codes to add each other as friends - similiar to how Nintendo handle friends in the past.

In order to achieve this we are thinking the simplest way is to - when a user is created - set their account username as the randomly generated code. The code will be the format of xxx-xxxx-xxx, all numbers so a simple 10-digit number (hyphens are not necessary for input)

This leads me to my question - how do we ensure that friend codes are unique and there are no conflicts. The chances of two randomly generated 10-digit numbers being the same are astronomically slim but not impossible!

One approach I started to explore was have a storage object dictionary which is keyed with all generated friend codes.

  • Read existing codes
  • Generate random code
  • Does code exist in dictionary?
    – yes: generate a new one and repeat
    – no: ok - assign this as the username
friendCode:= generateFriendCode()
usedFriendCodes := map[string]string{} // (read from nakama storage)
if _, ok := usedFriendCodes[friendCode]; ok {
   // friend code already used
   // return, try again
}

// Assign as used by this account
usedFriendCodes[friendCode] = accountId

This is fine but it leads to data duplication - an account is deleted then we need to also load this data and delete the friend code etc.

It seems the better approach would be to simply use the fact that usernames in Nakama must be unique. We can simply generate a friend code and assign it to their username by calling nk.AccountUpdateId and if this does not return an error then we know the code is unique. However, this leaves me slightly uncomfortable as my only way to determine what error has occurred is to query the message, which is plaintext english and could potentially change in future Nakama versions.

Is there a more reliable way to confirm the returned error? Is there a code I can query? Is there a different approach to this that I’m not seeing?

// Assign friendCode as their username
if err := nk.AccountUpdateId(ctx, "account-id", friendCode ...); err != nil {
   // How to reliably confirm the error here is "Username is already in use."
   // And not some other error which needs handling differently
}

// Example error:
{"level":"error","ts":"2026-01-06T11:13:35.309Z","caller":"server/core_account.go:257","msg":"Error updating user accounts.","error":"Username is already in use."}
  1. Versions: Nakama 3.35.1, Docker
  2. Server Framework Runtime language (If relevant) Go

Thank you!

Aha! I think I’ve found it after a little digging.

I believe this approach will provide me with what is needed:

if err := nk.AccountUpdateId(ctx, "account-id", "username", nil, "", "", "", "", ""); err != nil {
	status, ok := status.FromError(err)
    if ok && status.Code() == codes.AlreadyExists {
        // username already in use - retry
    } else {
        // some other internal error
    }
}

Requires importing the following packages

Hi @DWoodhouse22,

I’m not sure you can rely on the error being a statusError with that specific code if there’s a database username collision - from my brief inspection of the code it looks like we return a new error with the message Username is already in use. and do not wrap it with the correct status code in the runtime function, unless I’ve missed something (this error code path has been untouched for many years) - have you managed to surface an error with code codes.AlreadyExists on a username collision?

If I’m correct, for the time being, inspecting the error message is your best bet, I don’t expect the message to change, but we’ll see if we can return an error that can be unwrapped correctly as in your snippet in a future release.

Otherwise, the Authenticate* APIs all take an optional username param, if this is set in the request and the account doesn’t exist yet, this username is used for the newly created account, so you could try to set the username to the desired random 10 digit code at creation. The API should correctly surface the grpc AlreadyExists code in the http response message, or return a 409 HTTP status on a username conflict so that the client can retry with a different username.

Hi @sesposito, thanks for your reply!

No, I wasn’t able to extract a status code, I jumped the gun there!

We want to keep this logic on the server side rather than during the authenticate call from the client - makes it easier for us to control and make any changes without a client update in the future.

For now I will wrap this in a check for that error message, I don’t expect the message to change either but it would be great if a code could be surfaced to compare against.

I tried the following to see which code was being returned

if runtimeError, ok := err.(*runtime.Error); ok {
	logger.Debug("%v", runtimeError)
	continue
}

but this wasn’t successfully casting to runtime.Error, would you expect this to work ordinarily?

Ok understood - unfortunately at the moment you can only rely on the .Error() string, there’s no error to unwrap or sentinel error to compare against, we return a error.New("") if there’s a username collision in the AccountUpdateId runtime function, I’ll create an internal ticket to improve the returned error.

My suggestion would be to call AccountUpdateId in the relevant Authenticate* APIs as an after hook, only if created=true (so that it only tries to update the username for newly created accounts and not on follow-up auth).

1 Like