Server side Javascript in WebAssembly
Tags: webassembly, serverless, tutorial, javascript
Published: 2023-10-23 23:01:21

The nice thing about writing a serverless functions is that you only have to write an event handler. Much of the boilerplate code you'd write for a regular webserver-style app can be omitted.

In this post, we'll write a server-side Javascript function and then build it into a [WebAssembly](https://webassembly.org/) binary using the open source Spin tool. Our code will be less than a dozen lines long in total, so this is a concise introduction to WebAssembly and serverless functions that won't require you to spend a lot of time figuring out a code sample.

To get started, we need to install [Spin](https://developer.fermyon.com/spin/install) locally. Spin is a tool for building WebAssembly serverless functions, and then running them locally or deploying them to other runtimes. Spin will automatically download all of the supporting tools necessary for building Javascript apps into WebAssembly. You will need [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm), though, if you don't already have it.

Spin has four important commands:

* `spin new` scaffolds out a new project. It currently supports several languages. Today we'll do Javascript.
* `spin build` compiles a serverless function into WebAssembly.
* `spin up` starts a local copy of our WebAssembly serverless function so we can test it out.
* `spin deploy` can be used to deploy Spin to a production environment. There are also tools to [deploy to Docker Desktop](https://www.fermyon.com/blog/spin-in-docker) or [to Kubernetes](https://developer.fermyon.com/spin/kubernetes)

We'll use all four.

To get started, let's build a new JS app. We're going to name it `hello-dev`.

```console
$ spin new http-js hello-dev --accept-defaults
```

Above, we've used `spin new` to create a new `http-js` application. That means we are writing an HTTP event handler in JavaScript.

> Spin supports other handlers, like `redis` for listening on [Redis pubsub](https://redis.io/docs/interact/pubsub/) queues.

The `hello-dev` part is the name of our application. And the `--accept-defaults` flag tells Spin not to ask us interactive questions, but to instead just build us an application using the default settings.

Once the command is done, we have a directory named `hello-dev`. This is what we see inside of it:

```console
$ tree hello-dev/
hello-dev/
├── README.md
├── package.json
├── spin.toml
├── src
│   └── index.js
└── webpack.config.js

1 directory, 5 files
```

Here's what those files are for:

* `README.md` is just the place where we describe our doc.
* `package.json` is the [usual NPM-style configuration](https://docs.npmjs.com/cli/v6/configuring-npm/package-json) file.
* `spin.toml` is the [Spin configuration file](https://developer.fermyon.com/spin/manifest-reference). We don't have to change anything there today, but it's an important piece of a Spin application.
* `src/` contains our JS source, and we can see an `index.js` file there that we will edit in a bit.
* `webpack.config.js` is the [Webpack](https://webpack.js.org/) configuration file. When we build our app, Webpack is used to assemble it.

The first thing we need to do is change into the newly created `hello-dev/` directory and run `npm install` to set up the local environment for us.

```console
$ cd hello-dev
$ npm install

added 124 packages, and audited 125 packages in 7s

17 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
```

That installs all of the core libraries we need for today's serverless function.

Next up, open `src/index.js` in the editor of your choice. You'll see the following code:

```javascript
const encoder = new TextEncoder()

export async function handleRequest(request) {

    return {
        status: 200,
        headers: { "foo": "bar" },
        body: encoder.encode("Hello from JS-SDK").buffer
    }
}
```

This is just a very basic JS function. Spin will look for a function named `handleRequest()`, so it is important that the function is named so. The request will be given an HTTP request object (here called `request`), and Spin will expect that we return an HTTP response object, which we do in the code above using a literal object notion that has `status`, `headers`, and `body`.

> If you're new to HTTP, `status: 200` means success (`404` means `not found` and `500` means `server error`, and there are several others). Anything in `headers` will be accessible to the web browser, but not displayed to the user. and `body` will be displayed to the user in their browser.

_Without making any edits to the code_, we can build and test the application above.

First, we build with `spin build`:

```console
$ spin build
Building component hello-dev with `npm run build`

> hello-dev@1.0.0 build
> npx webpack --mode=production && mkdir -p target && spin js2wasm -o target/hello-dev.wasm dist/spin.js

asset spin.js 1.91 KiB [emitted] (name: main)
runtime modules 670 bytes 3 modules
./src/index.js 217 bytes [built] [code generated]
webpack 5.89.0 compiled successfully in 52 ms

Starting to build Spin compatible module
Preinitiating using Wizer
Optimizing wasm binary using wasm-opt
Spin compatible module built successfully
Finished building all Spin components
```

Once the above command is done, you should see a new directory called `target/`, and inside that directory is a binary called `hello-dev.wasm`.

```console
$ tree target
target
└── hello-dev.wasm

0 directories, 1 file
```

Now we can use `spin up` to start up our new `hello-dev.wasm` serverless function.

```console
$ spin up
Logging component stdio to ".spin/logs/"

Serving http://127.0.0.1:3000
Available Routes:
  hello-dev: http://127.0.0.1:3000 (wildcard)
```

This starts a new local server listening on port `3000`. So you can point your browser at that URL and see the result:

![Browser showing 'Hello from JS-SDK'](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kgya6jykao2myatlny2p.png)

That's our default serverless function! To stop the server, you can use `CTRL-C`.

Next up, let's change the code. We'll make it a little shorter, and add a custom message.

There are two things about the original code that we can do away with.

1. It's not actually necessary to use the `TextEncoder` to encode text. Spin does this for us.
2. We don't need a `header` in the response object.

So let's trim down the code and change the `body` to be something a little more personalized to this post:

```javascript
export async function handleRequest(request) {
    return {
        status: 200,
        body: "Hello Dev.to Developers!"
    }
}
```

We're down to a _six line_ serverless function. Once more, we can use `spin build` to build an updated WebAssembly binary, and then run `spin up` to start a local server.

Once more pointing our browser to `http://127.0.0.1:3000`, we can see our new text.

![Browser showing 'Hello Dev.to Developers!'](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ym4ejovyocfbnajpb2d4.png)

And there we have it! A Javascript serverless function in WebAssembly. If we wanted to deploy it to a server with a public URL, we could use `spin deploy`. By default, that will send it to Fermyon Cloud (using the free tier), and you'll be prompted to log in with GitHub. But you can also point Spin to other deployment platforms.

Here's a quick example:

```console
$ spin deploy
Uploading hello-dev version 0.1.0-rb6b8b1f3 to Fermyon Cloud...
Deploying...
Waiting for application to become ready........ ready
Available Routes:
  hello-dev: https://hello-dev-ftv5h3fg.fermyon.app (wildcard)
```

Now I've got an app available on a public endpoint, and you can access it at [https://hello-dev-ftv5h3fg.fermyon.app](https://hello-dev-ftv5h3fg.fermyon.app).

## Next Steps

Not everything that you find in the NPM catalog of libraries will work. Spin does not provide a 100% Node compatible runtime. But Spin does provide some things that Node.js and other JS runtimes do not:

* Built-in [Key Value Store](https://developer.fermyon.com/spin/kv-store-api-guide) means you don't have to solve the "stateless" problem. It's easy to store and retrieve text and JSON objects.
* Included [SQLite Database](https://developer.fermyon.com/spin/sqlite-api-guide) provides a SQL storage layer along with data migrations.
* [Serverless AI](https://developer.fermyon.com/spin/serverless-ai-api-guide) support means you can start coding applications that send prompts to LLMs like LLaMa2 and Code Llama. You can start writing AI apps very easily in Spin.

The best place to go from here is straight to the [Spin Javascript Language Guide](https://developer.fermyon.com/spin/javascript-components), where you can learn more about apps you can build.