Building a Serverless Python WebAssembly App with Spin
Tags: python, webassembly, serverless, tutorial
Published: 2023-10-17 23:10:06

In this post, we'll build a simple Serverless (a.k.a. "Functions as a Service") app with WebAssembly. We'll be using the [open source](https://github.com/fermyon/spin) Wasm tool called [Spin](https://developer.fermyon.com/spin).

If you haven't already, you will need to [install Spin](https://developer.fermyon.com/spin/install). I'm using the version of Python that is already installed on my Mac.

I am using VS Code with the Spin and Python extensions. However, that is all optional.

The first thing to do is to create a new Spin application.

```console
$ spin new http-py hello-spin
Description: Simple Hello example in Python
HTTP base: /
HTTP path: /...
```

The above will create a new directory called `hello-spin` with a bunch of files automatically scaffolded out for us:

```console
tree .
.
├── Pipfile
├── README.md
├── app.py
└── spin.toml

0 directories, 4 files
```

* `Pipfile` is the dependency file used by `pip`. This is pretty standard for Python projects.
* `README.md` is a project description. When building your own projects, you may want to edit that to describe how to build and use your app.
* `app.py` is the source code for our app. In a moment, we'll open and edit that file.
* `spin.toml` is the configuration file Spin uses to learn how to construct and run your app. We'll take a quick look at that now.

## Describing a serverless Wasm app with `spin.toml`

The `spin.toml` file looks like this:

```toml
spin_manifest_version = "1"
authors = ["Matt Butcher <matt.butcher@fermyon.com>"]
description = "Simple Hello example in Python"
name = "hello-spin"
trigger = { type = "http", base = "/" }
version = "0.1.0"

[[component]]
id = "hello-spin"
source = "app.wasm"
[component.trigger]
route = "/..."
[component.build]
command = "spin py2wasm app -o app.wasm"
watch = ["app.py", "Pipfile"]
```

The first block of lines are all basic data about the app. Who wrote it, what version of the SDK to use, and so on. Usually we don't need to change anything in there other than perhaps writing your own `description` or changing the `authors`.

Next, we have a `[[component]]`. Spin applications are composed of one or more components. You can think of a component in a few different ways:
1. As a microservice that is part of a bigger app (and gets deployed with that app)
2. As a stand-alone Wasm binary that gets run alongside other binaries
3. As a route on a routing table

In our case, we have one component named `hello-spin`. It is mapped to the route `/...` (which is a wildcard path meaning _anything under the root_). And when it is executed, it will run the (currently nonexistent) `app.wasm`.

There's a section called `[component.build]` which explains what happens to build this component when someone executes the project command `spin build` (which we'll do in a moment). You can see there that the build command is `spin py2wasm app -o app.wasm`. If we were writing a Rust or Javascript or Go app, the build command would be different, though most of the rest of this file would remain the same.

> You can [mix languages in a Spin app](https://developer.fermyon.com/spin/spin-application-structure).

The `watch` line at the bottom tells the command `spin watch` which files it should watch for changes. We won't be using `spin watch` as we build our app below.

That's it for the `spin.toml`. Let's write some Python!

## Coding a First Spin Python App

If we open `app.py`, we'll see that `spin new` already scaffolded us a basic Python app:

```python
from spin_http import Response


def handle_request(request):

    return Response(200,
                    {"content-type": "text/plain"},
                    bytes(f"Hello from the Python SDK", "utf-8"))

```
Spin builds serverless apps, which means we don't need to install or configure or start any sort of server in our code. Instead, the serverless runtime (Spin, in this case) will look for a specific function: `handle_request(request)`. That function is expected to return an instance of the `Response` object that we imported. That's all there is to a Spin Python app!

Without altering a line, we can build:

```console
$ spin build
Building component hello-spin with `spin py2wasm app -o app.wasm`
Spin-compatible module built successfully
Finished building all Spin components
```
Now you should see an `app.wasm` file, which is what the `spin.toml` points to.

Spin can act as a runtime for serverless functions locally. Let's start a local server:

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

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

We point a web browser at `http://127.0.0.1:3000` and see the results:


![Screenshot of browser saying Hello from the Python SDK](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/drwjh59alhb9xgs0n5vm.png)

To wrap up this intro tutorial, let's make a quick change and then rebuild and restart our app.

> You can use `CTRL-C` to stop `spin up`

Here's a quick change to our code:

```python
from spin_http import Response


def handle_request(request):

    return Response(200,
                    {"content-type": "text/plain"},
                    bytes(f"Hello Dev.to!", "utf-8"))
```

All we've done is change the body to say `Hello Dev.to`. Now let's build and start locally with one quick command:

```console
$ spin build --up
Building component hello-spin with `spin py2wasm app -o app.wasm`
Spin-compatible module built successfully
Finished building all Spin components
Logging component stdio to ".spin/logs/"

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

Once more, we now have a local copy of the app running. And we can refresh our browser window and see the result:

![Screenshot of browser showing hello dev.tom](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/onmur1rq8yy095kzxppa.png)

From here, we could edit the app to do things like [interact with the built-in Key Value Store](https://developer.fermyon.com/spin/kv-store-api-guide) or create some tables in the [built-in SQL Database](https://developer.fermyon.com/spin/sqlite-api-guide) or even [use the built-in LLM for some AI coding](https://www.fermyon.com/blog/serverless-ai-inferencing-using-python).

And whenever you are ready to deploy, you can run `spin deploy` to [send it to Fermyon Cloud](https://developer.fermyon.com/cloud/index), or you can deploy it into Docker Desktop, Kubernetes, Nomad, or other environments.