Building a Serverless Python WebAssembly App with Spin
Matt Butcher
Posted on October 17, 2023
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 Wasm tool called Spin.
If you haven't already, you will need to install Spin. 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.
$ 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:
tree .
.
├── Pipfile
├── README.md
├── app.py
└── spin.toml
0 directories, 4 files
-
Pipfile
is the dependency file used bypip
. 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:
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:
- As a microservice that is part of a bigger app (and gets deployed with that app)
- As a stand-alone Wasm binary that gets run alongside other binaries
- 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.
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:
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:
$ 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:
$ 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:
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 stopspin up
Here's a quick change to our code:
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:
$ 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:
From here, we could edit the app to do things like interact with the built-in Key Value Store or create some tables in the built-in SQL Database or even use the built-in LLM for some AI coding.
And whenever you are ready to deploy, you can run spin deploy
to send it to Fermyon Cloud, or you can deploy it into Docker Desktop, Kubernetes, Nomad, or other environments.
Posted on October 17, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.