DEV Community

Matt Butcher
Matt Butcher

Posted on

Server side Javascript in WebAssembly

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 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 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, 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 or to Kubernetes

We'll use all four.

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

$ spin new http-js hello-dev --accept-defaults
Enter fullscreen mode Exit fullscreen mode

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 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:

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

1 directory, 5 files
Enter fullscreen mode Exit fullscreen mode

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 file.
  • spin.toml is the Spin configuration file. 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 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.

$ 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
Enter fullscreen mode Exit fullscreen mode

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:

const encoder = new TextEncoder()

export async function handleRequest(request) {

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

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:

$ 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
Enter fullscreen mode Exit fullscreen mode

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.

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

0 directories, 1 file
Enter fullscreen mode Exit fullscreen mode

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

$ 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)
Enter fullscreen mode Exit fullscreen mode

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'

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:

export async function handleRequest(request) {
    return {
        status: 200,
        body: "Hello Dev.to Developers!"
    }
}
Enter fullscreen mode Exit fullscreen mode

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!'

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:

$ 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)
Enter fullscreen mode Exit fullscreen mode

Now I've got an app available on a public endpoint, and you can access it at 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 means you don't have to solve the "stateless" problem. It's easy to store and retrieve text and JSON objects.
  • Included SQLite Database provides a SQL storage layer along with data migrations.
  • Serverless AI 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, where you can learn more about apps you can build.

Top comments (4)

Collapse
 
artxe2 profile image
Yeom suyun

Spin is quite interesting, but is it practical too?

Collapse
 
technosophos profile image
Matt Butcher

You can get some ideas of what can be built here: developer.fermyon.com/hub

Collapse
 
pxlmastrxd profile image
Pxlmastr

Cool, great post!

Collapse
 
meteatamel profile image
Mete Atamel

Can you explain what happens under the covers? How does Node.js code compile to a Wasm module with Spin?