Component Templates with Spin
MacKenzie Olson
spin
wasm
Howdy Fermyon friends! It’s that time of year for Spring cleaning, so I’m into reducing, reusing, and recycling. To stay on theme, I will take us through a deep dive into Spin component reuse. For those unfamiliar, a Spin component is a library or program that implements your event-handling logic and uses Spin interfaces, libraries, or tools to associate that with the events Spin handles. Component reuse comes in handy when you’re looking to extend your application’s functionality in a specific manner. For example, let’s say you have a Spin application and would like it to serve static content as well. Rather than write your own static file server, you can use Spin’s static file server template component. Let’s look into the mechanics of adding a component to your existing Spin application and how you can write your component template too!
Pick Up Development Speed - spin add
In Spin v0.8.0, we introduced spin add
, which allows you to add a new component to your existing Spin application. What sort of scenarios would benefit from this functionality? Rather than having to write that logic from scratch, you can use spin add
to add that component to your Spin application - saving you time and extending the functionality of your Spin application all in one quick action.
To discover reusable components written by Fermyon, you can install our template using the commands below:
# update the latest templates and install all templates used:
spin templates install --git https://github.com/fermyon/spin --update
spin templates install --git https://github.com/fermyon/spin-js-sdk --update
By running spin add
with the desired template name from within your Spin application’s directory, you will add the component(s) associated with the template to your existing Spin application:
# create a new empty application
spin new http-empty && cd app
# now add a JavaScript component
spin add http-js
# and a Go component
spin add http-go
Spin templates are discoverable via the spin template --list
command:
spin templates list
+-----------------------------------------------------------------------------+
| Name Description |
+=============================================================================+
| qr-generator Generate QR Codes |
| redirect Redirects a HTTP route |
| redis-go Redis message handler using (Tiny)Go |
| redis-rust Redis message handler using Rust |
| static-fileserver Serves static files from an asset directory |
+-----------------------------------------------------------------------------+
Extending Spin - Writing Your Own Templates
The Workload - Shortlink Generator
You don’t have to rely exclusively on Fermyon for your templated component needs. Let’s walk through an example together to learn how to write and share your own templated components. You can see the complete example on GitHub. Below we have a Spin application that creates shortlinks as well as a simple admin UI to manage the shortlinks.
spin_manifest_version = "1"
authors = ["Mikkel Mørk Hegnhøj <mikkel@fermyon.com>"]
description = "Short link redirector"
name = "redirect"
trigger = { type = "http", base = "/" }
version = "0.1.0"
[[component]]
id = "api"
source = "api/target/wasm32-wasi/release/api.wasm"
key_value_stores = ["default"]
[component.trigger]
route = "/api/..."
[component.build]
command = "cargo build --target wasm32-wasi --release"
workdir = "api"
[[component]]
source = { url = "https://github.com/fermyon/spin-fileserver/releases/download/v0.0.2/spin_static_fs.wasm", digest = "sha256:65456bf4e84cf81b62075e761b2b0afaffaef2d0aeda521b245150f76b96421b" }
id = "client"
files = [ {source = "client", destination = "/" } ]
[component.trigger]
route = "/admin/..."
[[component]]
id = "redirect"
source = "redirect/target/wasm32-wasi/release/redirect.wasm"
key_value_stores = ["default"]
[component.trigger]
route = "/..."
[component.build]
command = "cargo build --target wasm32-wasi --release"
workdir = "redirect"
watch = ["src/**/*.rs", "Cargo.toml", "spin.toml"]
As the next iteration of this app, we’d like to add a QR code scanner. With the QR code scanner, our users can scan swag, and business card handouts, and this will continue to be fed into our shortlink app. Recognizing that others may benefit from adding QR code functionality to their Spin applications, we create a template for this component.
Writing Custom Component Template - QR Code Generator
First, we’ll create a separate public repository to host the QR code generator. This way, other Spin developers can checkout the component and provide feedback without having access to our entire application. Like all Spin applications, we’ll include our source code and application manifest (spin.toml
):
spin-qr-generator % ls
LICENSE README.md qr-generator qr.png spin.toml
To test the application out, we’ll take it for a quick spin with the spin build --up
. This Spin component exposes an HTTP endpoint to generate QR codes. The component takes a URL-encoded query parameter and returns an SVG element:
curl -i http://localhost?github.com%2Ffermyon%2Fspin
HTTP/1.1 200 OK
content-length: 29817
date: Tue, 04 Apr 2023 09:01:37 GMT
...
Now that we’re satisfied with our business logic, it’s time to create a template so other Spin developers can use this component. A Spin template requires two directories: content
and metadata
:
content
- this directory must contain all the files you’d like copied into the application directory. Examples include source code, spin.toml
, standard assets, precompiled modules, etc.
metadata
- this directory must contain the files that control how the template is instantiated, likely this should just be the template manifest.
Tip: Both the content and metadata directories must be located under a parent directory with a descriptive title (in this case, we’ll use qr-code-generator
). This will serve as the template. This directory must go under a parent directory called templates
.
Let’s take a look at what we’ve stored in our content directory. To simplify things, we have included only the spin.toml
which points to the compiled Wasm module file:
spin_manifest_version = "1"
authors = ["Mikkel Mørk Hegnhøj <mikkel@fermyon.com>"]
description = "QR Code generator for Spin"
name = "spin-qr-generator"
trigger = { type = "http", base = "/" }
version = "0.0.1"
[[component]]
id = "spin-qr-generator"
source = "qr-generator/target/wasm32-wasi/release/spin_qr_generator.wasm"
[component.trigger]
route = "/..."
[component.build]
command = "cargo build --target wasm32-wasi --release"
workdir = "qr-generator"
Now let’s take a look into the metadata
directory. We’ve added two files as you can see below:
metadata % ls
snippets spin-template.toml
spin-template
is our template manifest, as mentioned above. It includes metadata about the application such as the manifest_version
, id
, description
, and tags
. If you added placeholders in your content/spin.toml
, you will need to include these along with their values here.
snippet
directory has been added to support the spin add
command, which will allow users to add our template as a new component to their Spin application:
[[component]]
source = { url = "https://github.com/mikkelhegn/spin-qr-generator/releases/download/v0.0.1/spin_qr_generator.wasm", digest = "sha256:33d922ffe15e07a230af314ba6b7d781ed72b7de895982132ef923b3424f094f" }
id = "spin-qr-generator"
[component.trigger]
route = "/qr/..."
With the addition of those two directories, our template is officially ready for others to use.
Adding QR Code Component to Shortlink Generator Application
Now that we’ve published our QR Code component, let’s add it to our shortlink Spin application. To do so, we’ll navigate to our shortlink Spin application and run the following commands to install the template and then add the component:
spin templates install --git https://github.com/mikkelhegn/spin-qr-generator
spin add qr-generator qr --accept-defaults
Now if we navigate to our spin.toml
we’ll see that the qr-generator component has been added to our Spin app:
spin_manifest_version = "1"
authors = ["Mikkel Mørk Hegnhøj <mikkel@fermyon.com>"]
description = "Short link redirector"
name = "redirect"
trigger = { type = "http", base = "/" }
version = "0.1.0"
[[component]]
id = "api"
source = "api/target/wasm32-wasi/release/api.wasm"
key_value_stores = ["default"]
[component.trigger]
route = "/api/..."
[component.build]
command = "cargo build --target wasm32-wasi --release"
workdir = "api"
[[component]]
source = { url = "https://github.com/fermyon/spin-fileserver/releases/download/v0.0.2/spin_static_fs.wasm", digest = "sha256:65456bf4e84cf81b62075e761b2b0afaffaef2d0aeda521b245150f76b96421b" }
id = "client"
files = [ {source = "client", destination = "/" } ]
[component.trigger]
route = "/admin/..."
[[component]]
id = "redirect"
source = "redirect/target/wasm32-wasi/release/redirect.wasm"
key_value_stores = ["default"]
[component.trigger]
route = "/..."
[component.build]
command = "cargo build --target wasm32-wasi --release"
workdir = "redirect"
watch = ["src/**/*.rs", "Cargo.toml", "spin.toml"]
[[component]]
source = { url = "https://github.com/mikkelhegn/spin-qr-generator/releases/download/v0.0.1/spin_qr_generator.wasm", digest = "sha256:33d922ffe15e07a230af314ba6b7d781ed72b7de895982132ef923b3424f094f" }
id = "spin-qr-generator"
[component.trigger]
route = "/qr/..."
After running spin build --up
we’ll see our application in action:
spin build --up
...
Successfully ran the build command for the Spin components.
Logging component stdio to ".spin/logs/"
Storing key-value data to ".spin/sqlite_key_value.db"
Serving http://127.0.0.1:3000
Available Routes:
api: http://127.0.0.1:3000/api (wildcard)
client: http://127.0.0.1:3000/admin (wildcard)
redirect: http://127.0.0.1:3000 (wildcard)
spin-qr-generator: http://127.0.0.1:3000/qr (wildcard)
When we hit the client endpoint, we see our shortlink Spin application is successfully up and running!
Summary
Fermyon friends, that’s it for now! Thank you for following along. I hope this blog has inspired you to create your own custom templates. If you decide to give it a try, we’d love to see what you’ve built in our Discord community. Happy templating!