# Code Samples

**URL:** https://heroiclabs.com/docs/nakama/server-framework/typescript-runtime/code-samples/
**Summary:** Development examples for the TypeScript runtime.
**Keywords:** nakama typescript development, sql, rpc, registerrpc, setup
**Categories:** nakama, code-samples, typescript-runtime

---


# Code Samples

This page provides some common examples for the functionality available that can be used as templates when developing your project using the TypeScript runtime.

## Match handler

A match handler represents all server-side functions for handling game inputs and operations for [authoritative multiplayer](../../../concepts/multiplayer/authoritative/) matches. See the [Match Handler API](../function-reference/match-handler/) and [Match Runtime API](../function-reference/match-runtime/) reference pages to learn about the match handler functions.

This is an example of a Ping-Pong match handler. Messages received by the server are broadcast back to the peer who sent them.

```typescript
const matchInit = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, params: {[key: string]: string}): {state: nkruntime.MatchState, tickRate: number, label: string} {
  logger.debug('Lobby match created');

  const presences: {[userId: string]: nkruntime.Presence} = {};

  return {
    state: { presences },
    tickRate: 1,
    label: ''
  };
};

const matchJoinAttempt = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presence: nkruntime.Presence, metadata: {[key: string]: any }) : {state: nkruntime.MatchState, accept: boolean, rejectMessage?: string | undefined } | null {
  logger.debug('%q attempted to join Lobby match', ctx.userId);

  return {
    state,
    accept: true
  };
}

const matchJoin = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]) : { state: nkruntime.MatchState } | null {
  presences.forEach(function (presence) {
    state.presences[presence.userId] = presence;
    logger.debug('%q joined Lobby match', presence.userId);
  });

  return {
    state
  };
}

const matchLeave = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]) : { state: nkruntime.MatchState } | null {
  presences.forEach(function (presence) {
    delete (state.presences[presence.userId]);
    logger.debug('%q left Lobby match', presence.userId);
  });

  return {
    state
  };
}

const matchLoop = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, messages: nkruntime.MatchMessage[]) : { state: nkruntime.MatchState} | null {
  logger.debug('Lobby match loop executed');

  Object.keys(state.presences).forEach(function (key) {
    const presence = state.presences[key];
    logger.info('Presence %v name $v', presence.userId, presence.username);
  });

  messages.forEach(function (message) {
    logger.info('Received %v from %v', message.data, message.sender.userId);
    dispatcher.broadcastMessage(1, message.data, [message.sender], null);
  });

  return {
    state
  };
}

const matchTerminate = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, graceSeconds: number) : { state: nkruntime.MatchState} | null {
  logger.debug('Lobby match terminated');

  const message = `Server shutting down in ${graceSeconds} seconds.`;
  dispatcher.broadcastMessage(2, message, null, null);

  return {
    state
  };
}

const matchSignal = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, data: string) : { state: nkruntime.MatchState, data?: string } | null {
  logger.debug('Lobby match signal received: ' + data);

  return {
    state,
    data: "Lobby match signal received: " + data
  };
}
```

## Context

This example demonstrates extracting the ID of the calling user and a key stored as an environment variable:

```typescript
// Getting the calling user ID from the context
const userId = ctx.userId;

// Getting the environment variables from the context
const env = ctx.env;
const secretKey = env["SECRET_KEY"];
if !secretKey {
  // Did not find the environment variable
}
```

## Database handler

This example creates a system ID - an ID that cannot be used from a client - and the custom SQL query inserting it in the `users` table:

```typescript
let InitModule: nkruntime.InitModule =
        function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
    let systemId: string = ctx.env["SYSTEM_ID"]

    nk.sqlExec(`
INSERT INTO users (id, username)
VALUES ($1, $2)
ON CONFLICT (id) DO NOTHING
    `, { systemId, "system_id" })

    logger.Info('system id: %s', systemId)
}
```

## RPC

The example below registers a function with the identifier `custom_rpc_func_id`. This ID can then be used within client code to send an RPC message to execute the function on the server and return the result.

```typescript
let customFuncRpc: nkruntime.RpcFunction =
function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string) {
	logger.info('payload: %q', payload);

	// "payload" is bytes sent by the client we'll JSON decode it.
	let json = JSON.parse(payload);

	return JSON.stringify(json);
}

// Register as an after hook for the appropriate feature, this call should be in InitModule.
initializer.registerRpc("custom_rpc_func_id", customFuncRpc);
```

## Before hook

The code example below fetches the current user's profile and checks the metadata, which is assumed to be JSON encoded with `"{level: 12}"` in it. If a user's level is too low, an error is thrown to prevent the Friend Add message from being passed onwards in the server:

```typescript
let userAddFriendLevelCheck: nkruntime.BeforeHookFunction<AddFriendsRequest> =
function(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, data: nkruntime.AddFriendsRequest): nkruntime.AddFriendsRequest {
	let userId = ctx.userId;

	let users: nkruntime.User[];
	try {
			users = nk.usersGetId([ userId ]);
	} catch (error) {
			logger.error('Failed to get user: %s', error.message);
			throw error;
	}

	// Let's assume we've stored a user's level in their metadata.
	if (users[0].metadata.level < 10) {
			throw Error('Must reach level 10 before you can add friends.');
	}

	// important!
	return data;
};

// Register as an after hook for the appropriate feature, this call should be in InitModule.
initializer.registerBeforeAddFriends(userAddFriendLevelCheck);
```

## After hook

The example code below writes a record to a user's storage when they add a friend. Any data returned by the function will be discarded.

```typescript
// The AddFriends function does not return a payload, hence why the outPayload argument is null.
let afterAddFriends: nkruntime.AfterHookFunction<null, AddFriendsRequest> =
function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, outPayload: null, inPayload: nkruntime.AddFriendsRequest) {
	let userId = ctx.userId;
	if (!userId) {
			throw Error('Missing user ID.');
	}

	let userIds = inPayload.ids;
	let storageObj: nkruntime.StorageWriteRequest = {
			collection: 'rewards',
			key: 'reward',
			userId: userId,
			value: { userIds },
	};

	try {
			nk.storageWrite([ storageObj ]);
	} catch (error) {
			logger.error('Error writing storage object: %s', error.message);
			throw error;
	};

	return null; // Can be omitted, will return `undefined` implicitly
};

// Register as an after hook for the appropriate feature, this call should be in InitModule.
initializer.registerAfterAddFriends(afterAddFriends);
```

## Example module

As a fun example let's use the [Pokéapi](http://pokeapi.co/) and build a helpful module.

```typescript
const apiBaseUrl = 'https://pokeapi.co/api/v2';

let InitModule: nkruntime.InitModule =
function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer) {
	initializer.registerRpc('get_pokemon', getPokemon);
};

function lookupPokemon(nk: nkruntime.Nakama, name: string) {
	let url = apiBaseUrl + '/pokemon/' + name;
	let headers = { 'Accept': 'application/json' };
	let response = nk.httpRequest(url, 'get', headers);

	return JSON.parse(response.body);
}

let getPokemon: nkruntime.RpcFunction =
function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string) {
	// We'll assume payload was sent as JSON and decode it.
	let json = JSON.parse(payload);
	let pokemon;

	try {
		pokemon = lookupPokemon(nk, json['PokemonName']);
	} catch (error) {
		logger.error('An error occurred looking up pokemon: %s', error.message);
		throw error;
	}

	let result = {
		name: pokemon.name,
		height: pokemon.height,
		weight: pokemon.weight,
		image: pokemon.sprites.front_default,
	}

	return JSON.stringify(result);
}
```

You can now make an RPC call for a Pokémon from a client:

{{< code type="client" >}}
```bash
curl "http://127.0.0.1:7350/v2/rpc/get_pokemon" \
  -H 'authorization: Bearer <session token>'
  -d '"{\"PokemonName\": \"dragonite\"}"'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
const payload = { "PokemonName": "dragonite"};
const rpcid = "get_pokemon";
const pokemonInfo = await client.rpc(session, rpcid, payload);
console.log("Retrieved pokemon info: %o", pokemonInfo);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var payload = JsonWriter.ToJson(new { PokemonName = "dragonite" });
var rpcid = "get_pokemon";
var pokemonInfo = await client.RpcAsync(session, rpcid, payload);
System.Console.WriteLine("Retrieved pokemon info: {0}", pokemonInfo);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NRpc& rpc)
{
  	std::cout << "Retrieved pokemon info: " << rpc.payload << std::endl;
};

string payload = "{ \"PokemonName\": \"dragonite\" }";
string rpcid = "get_pokemon";
client->rpc(session, rpcid, payload, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
Map<String, String> payloadData = new HashMap<>();
payloadData.put("PokemonName", "dragonite");
String payload = new Gson().toJson(payloadData, payloadData.getClass());
String rpcid = "get_pokemon";
Rpc pokemonInfo = client.rpc(session, rpcid, payload);
System.out.format("Retrieved pokemon info: %s", pokemonInfo.getPayload());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var payload = {"PokemonName": "dragonite"}
var rpc_id = "get_pokemon"
var pokemon_info : NakamaAPI.ApiRpc = yield(client.rpc_async(session, rpc_id, JSON.print(payload)), "completed")
if pokemon_info.is_exception():
	print("An error occurred: %s" % pokemon_info)
	return
print("Retrieved pokemon info: %s" % [parse_json(pokemon_info.payload)])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var payload = {"PokemonName": "dragonite"}
var rpc_id = "get_pokemon"
var pokemon_info : NakamaAPI.ApiRpc = await client.rpc_async(session, rpc_id, JSON.stringify(payload))
if pokemon_info.is_exception():
	print("An error occurred: %s" % pokemon_info)
	return
print("Retrieved pokemon info: %s" % [JSON.parse_string(pokemon_info.payload)])
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/rpc/get_pokemon
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
  "PokemonName": "dragonite"
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local payload = { PokemonName = "dragonite"}
local rpcid = "get_pokemon"
local pokemon_info = client.rpc_func(rpcid, json.encode(payload)) 
pprint("Retrieved pokemon info:", pokemon_info)
```
{{< / code >}}
