# Serverless Functions: Node.js Code Execution with ROMs

import { Tabs, TabsContent, TabsList, TabsTrigger } from "zudoku/ui/Tabs"

{/* vale off */}
{/* THIS FILE WAS AUTOGENERATED FROM THE PUBLIC EXAMPLE REPOSITORY. DO NOT EDIT THIS FILE DIRECTLY. */}


Serverless functions let you deploy small pieces of business logic without managing servers or runtimes.
This example implements that model for Node.js, keeping the function code separate from the runtime image that executes it, so you update a function without rebuilding the runtime.

This guide explains how to deploy TypeScript/JavaScript functions as auxiliary Read-Only Memory (ROM) images, then load them dynamically in a Node.js runtime.
With Unikraft Cloud, you can create a base image with a generic runtime, package custom code as ROMs, and attach different ROMs to instances of the same base image.

## Prerequisites

1. Install the CLI:
   Use the [unikraft CLI](/cli/unikraft) or the legacy [kraft CLI](https://unikraft.org/docs/cli/install).
   You need a [BuildKit](https://github.com/moby/buildkit) builder. The easiest way is via [Docker](https://docs.docker.com/engine/install/).
   Alternatively, set up and use BuildKit directly, see the [quick start](https://github.com/moby/buildkit#quick-start).

2. Clone the [`examples` repository](https://github.com/unikraft-cloud/examples) and `cd` into the `examples/node-code-execution` directory:

   ```bash
   git clone https://github.com/unikraft-cloud/examples
   cd examples/node-code-execution/
   ```

Make sure to log into Unikraft Cloud and pick a [metro](/platform/metros) close to you.
This guide uses `fra` (Frankfurt, 🇩🇪):

<CodeTabs syncKey="cli">

```bash title="unikraft"
unikraft login
```

```bash title="kraft"
# Set Unikraft Cloud access token
export UKC_TOKEN=token
export UKC_METRO=fra
```

</CodeTabs>

## Deployment Workflow

### Package the base image

First, package and push the base Node.js image (see `server.ts` for the runtime implementation):

<CodeTabs syncKey="cli">

```bash title="unikraft"
unikraft build . --output <my-org>/node-code-exec:latest
```

```bash title="kraft"
kraft pkg \
  --name index.unikraft.io/<my-org>/node-code-exec:latest \
  --plat kraftcloud \
  --arch x86_64 \
  --rootfs-type erofs \
  --push \
  .
```

</CodeTabs>

The server implementation in `server.ts` is a simple Node.js application that listens for HTTP requests and executes JavaScript code from the attached ROM, if available.
There is a little tweak—right before loading the ROM code and starting the server, it writes `1` to the special file `/uk/libukp/template_instance` (see /platform/instances#instance-templates), triggering a conversion of the instance into a template.

### Create an instance template from the base image

Create an instance that uses the base Node.js image without any ROM attached:

<CodeTabs syncKey="cli">

```bash title="unikraft"
unikraft run --metro fra \
  --name node-exec \
  -m 512M \
  --image <my-org>/node-code-exec:latest
```

```bash title="kraft"
kraft cloud instance create \
  --start \
  --name node-exec \
  -M 512Mi \
  <my-org>/node-code-exec:latest
```

</CodeTabs>

The output shows the instance details:

<CodeTabs syncKey="cli">

```ansi title="unikraft"
[1mmetro[22m:        fra
[1mname[22m:         node-exec
[1muuid[22m:         96608ed2-45e0-4c8f-8269-5d8cd3e4b41a
[1mstate[22m:        [38;2;144;161;185mstarting[0m
[1mimage[22m:        <my-org>/node-code-exec
[1mresources[22m:
[3m  memory[23m:     512MiB
[3m  vcpus[23m:      1
[1mnetworks[22m:
[3m- uuid[23m:       6f7a8b9c-0d1e-2f3a-4b5c-f6a7b8c9d0e1
[3m  private-ip[23m: 10.0.5.4
[3m  mac[23m:        12:b0:6c:3e:ab:95
[1mtimestamps[22m:
[3m  created[23m:    just now
```

```ansi title="kraft"
[38;5;245m[[0m[92m●[0m[38;5;245m][0m Deployed successfully!
 [38;5;245m│[0m
 [38;5;245m├[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m [38;5;245mname[0m: node-exec
 [38;5;245m├[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m [38;5;245muuid[0m: 96608ed2-45e0-4c8f-8269-5d8cd3e4b41a
 [38;5;245m├[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m [38;5;245mmetro[0m: https://api.fra.unikraft.cloud/v1
 [38;5;245m├[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m [38;5;245mstate[0m: [92mstarting[0m
 [38;5;245m├[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m [38;5;245mimage[0m: oci://unikraft.io/<my-org>/node-code-exec@sha256:71487fd6196987cf65fb89eb84405cb796677aba177dabacf391f09618313328
 [38;5;245m├[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m [38;5;245mmemory[0m: 512 MiB
 [38;5;245m├[0m[38;5;245m─[0m [38;5;245mprivate fqdn[0m: node-exec.internal
 [38;5;245m└[0m[38;5;245m─[0m[38;5;245m─[0m[38;5;245m─[0m [38;5;245mprivate ip[0m: 10.0.5.4
```

</CodeTabs>

This instance is short-lived, since right before the server starts, it triggers a conversion into a template.
To check that the template is ready, run:

<CodeTabs syncKey="cli">

```bash title="unikraft"
unikraft instances templates list
```

```bash title="unikraft"
[1mMETRO[22m  [1mNAME[22m       [1mSTATE[22m     [1mIMAGE[22m                    [1mARGS[22m  [1mMEMORY[22m  [1mVCPUS[22m  [1mCREATED[22m
fra    node-exec  [38;2;43;127;255mtemplate[0m  <my-org>/node-code-exec        512MiB  1      5 seconds ago
```

```bash title="kraft"
kraft cloud instance template list
```

```bash title="kraft"
[0;1;39mNAME[0m       [0;1;39mIMAGE[0m                                                                                                              [0;1;39mARGS[0m  [0;1;39mCREATED AT[0m
node-exec  oci://unikraft.io/<my-org>/node-code-exec@sha256:71487fd6196987cf65fb89eb84405cb796677aba177dabacf391f09618313328        5 seconds ago
```

</CodeTabs>

### Package the ROMs

Create and push the ROMs with the code (see `rom1/fs/rom.js` and `rom2/fs/rom.ts`):

<CodeTabs syncKey="cli">

```bash title="unikraft"
unikraft build rom1/ --output <my-org>/node-rom1:latest
unikraft build rom2/ --output <my-org>/node-rom2:latest
```

```bash title="kraft"
kraft pkg \
  --name index.unikraft.io/<my-org>/node-rom1:latest \
  --rom ./fs \
  --rom-type erofs \
  --plat kraftcloud \
  --arch x86_64 \
  --push \
  rom1/
kraft pkg \
  --name index.unikraft.io/<my-org>/node-rom2:latest \
  --rom ./fs \
  --rom-type erofs \
  --plat kraftcloud \
  --arch x86_64 \
  --push \
  rom2/
```

</CodeTabs>

### Create instances from the template with different ROMs attached

Create a new instance from the template, attaching the first ROM:

<CodeTabs syncKey="cli">

```bash title="unikraft"
unikraft run --metro fra \
  --name node-exec-rom1 \
  -p 443:8080/tls+http \
  --scale-to-zero policy=on,cooldown-time=1000,stateful=true \
  --rom image=<my-org>/node-rom1:latest,at=/rom \
  --template node-exec
```

```bash title="kraft"
# kraft does not support creating instances with attached ROMs, but you can use the API directly
curl -X POST "$UKC_METRO/instances" \
   -H "Accept: application/json" \
   -H "Authorization: Bearer $UKC_TOKEN" \
   -H "Content-Type: application/json" \
   -d '{
   "name": "node-exec-rom1",
   "template": {
      "name": "node-exec"
   },
   "autostart": true,
   "service_group": {
      "services": [
         {
            "port": 443,
            "destination_port": 8080,
            "handlers": ["tls", "http"]
         }
      ]
   },
   "scale_to_zero": {
      "policy": "on",
      "stateful": true,
      "cooldown_time_ms": 1000
   },
   "roms": [
      {
         "name": "js_function",
         "image": "index.unikraft.io/<my-org>/node-rom1:latest",
         "at": "/rom"
      }
   ]
}'
```

</CodeTabs>

Create another instance from the same template, but with the second ROM attached:

<CodeTabs syncKey="cli">

```bash title="unikraft"
unikraft run --metro fra \
  --name node-exec-rom2 \
  -p 443:8080/tls+http \
  --scale-to-zero policy=on,cooldown-time=1000,stateful=true \
  --rom image=<my-org>/node-rom2:latest,at=/rom \
  --template node-exec
```

```bash title="kraft"
# kraft does not support creating instances with attached ROMs, but you can use the API directly
curl -X POST "$UKC_METRO/instances" \
   -H "Accept: application/json" \
   -H "Authorization: Bearer $UKC_TOKEN" \
   -H "Content-Type: application/json" \
   -d '{
   "name": "node-exec-rom2",
   "template": {
      "name": "node-exec"
   },
   "autostart": true,
   "service_group": {
      "services": [
         {
            "port": 443,
            "destination_port": 8080,
            "handlers": ["tls", "http"]
         }
      ]
   },
   "scale_to_zero": {
      "policy": "on",
      "stateful": true,
      "cooldown_time_ms": 1000
   },
   "roms": [
      {
         "name": "ts_function",
         "image": "index.unikraft.io/<my-org>/node-rom2:latest",
         "at": "/rom"
      }
   ]
}'
```

</CodeTabs>

List the instances and note their FQDN values:

<CodeTabs syncKey="cli">

```bash title="unikraft"
unikraft instances list
```

```bash title="unikraft"
[1mMETRO[22m  [1mNAME[22m            [1mSTATE[22m    [1mIMAGE[22m                    [1mARGS[22m  [1mMEMORY[22m  [1mVCPUS[22m  [1mFQDN[22m                                      [1mCREATED[22m
fra    node-exec-rom2  [38;2;43;127;255mstandby[0m  <my-org>/node-code-exec        512MiB  1      nameless-wood-gw7pbnls.fra.unikraft.app   2 minutes ago
fra    node-exec-rom1  [38;2;43;127;255mstandby[0m  <my-org>/node-code-exec        512MiB  1      sparkling-dawn-syowlbtj.fra.unikraft.app  3 minutes ago
```

```bash title="kraft"
kraft cloud instance list
```

```bash title="kraft"
[0;1;39mNAME[0m            [0;1;39mFQDN[0m                                      [0;1;39mSTATE[0m    [0;1;39mSTATUS[0m   [0;1;39mIMAGE[0m                                                       [0;1;39mMEMORY[0m   [0;1;39mVCPUS[0m  [0;1;39mARGS[0m  [0;1;39mBOOT TIME[0m
node-exec-rom2  nameless-wood-gw7pbnls.fra.unikraft.app   [0;36mstandby[0m  standby  oci://unikraft.io/<my-org>/node-code-exec@sha256:71487f...  512 MiB  1            6.98 ms
node-exec-rom1  sparkling-dawn-syowlbtj.fra.unikraft.app  [0;36mstandby[0m  standby  oci://unikraft.io/<my-org>/node-code-exec@sha256:71487f...  512 MiB  1            7.86 ms
```

</CodeTabs>

Test both instances:

```bash
curl https://sparkling-dawn-syowlbtj.fra.unikraft.app
curl https://nameless-wood-gw7pbnls.fra.unikraft.app
```

```text
Bye, World!
Auf Wiedersehen!
```

## Learn more

Use the `--help` option for detailed information on using Unikraft Cloud:

<CodeTabs syncKey="cli">

```bash title="unikraft"
unikraft --help
```

```bash title="kraft"
kraft cloud --help
```

</CodeTabs>

Or visit the [CLI Reference](/cli/unikraft) or the [legacy CLI Reference](/cli/kraft/overview).
{/* vale on */}
