Writing Spin Applications

A Spin application consists of a set of WebAssembly (Wasm) components, and a manifest that lists those components with some data about when and how to run them. This page describes how to write components, manifests, and hence applications.

Writing an Application Manifest

You won’t normally create a manifest by hand, but will instead create or update one from a template. Skip to those sections if that’s what you want to do. But it’s important to know what’s inside a manifest for when you need to update component capabilities or troubleshoot a problem.

A Spin application manifest is a TOML file that contains:

  • Identification information about the application
  • What events the application should run in response to (the triggers)
  • The Wasm modules of the application and their associated settings (the components)
  • Optionally, configuration variables that the user can set when running the application

By convention, the Spin manifest file is usually called spin.toml.

This example is the manifest for a simple HTTP application with a single trigger executed when the /hello endpoint is accessed:

spin_manifest_version = 2

# General identification information
[application]
name = "spin-hello-world"
version = "1.0.0"
description = "A simple application that returns hello world."

# The application's sole trigger. This application responds to HTTP requests
# on the path "/hello", and handles them using the "hello" component.
[[trigger.http]]
route = "/hello"
component = "hello"

# The "hello" component
[component.hello]
description = "A simple component that returns hello world."
# The Wasm module to run for the component
source = "target/wasm32-wasip1/release/helloworld.wasm"
# How to build the Wasm module from source
[component.hello.build]
command = "cargo build --target wasm32-wasip1 --release"

You can look up the various fields in the Manifest Reference, but let’s look at the key fields in more detail.

The trigger Fields

An application contains one or more triggers. Each trigger specifies a type, an event of that type that the application responds to, and a component to handle that event.

The description above might sound a bit abstract. Let’s clarify it with a concrete example. The most common trigger type is http, for which events are distinguished by the route. So an HTTP trigger might look like this:

# double square brackets because there could be multiple 'trigger.http' tables
[[trigger.http]]       # the type is part of the TOML "section"
route = "/hello"       # the route (event) that this trigger responds to
component = "greeter"  # the component to handle the trigger

Put the type (http), event (route = "/hello"), and component (greeter) together, and this is saying “when the application gets an HTTP request to /hello, respond to it using the greeter component.

For more details about http triggers, see the HTTP trigger documentation.

Another trigger type is redis, which is triggered by Redis pub-sub messages. For the redis type, the trigger event is specified by a pub-sub channel, e.g. channel = "alerts". See the Redis trigger documentation for more information.

Multiple triggers may refer to the same component. For example, you could have another trigger on /dober-dan which also invokes the greeter component.

Some triggers have additional application-level configuration options. For example, the Redis trigger allows you to provide an address field, which tells Spin the default server for components that do not specify servers. See the Redis trigger documentation for more details.

The Component Name

Component names are identifier strings. While not significant in themselves, they must be unique within the application. Triggers use names to refer to components, and Spin uses the name in logging and error messages to tell you which component a message applies to.

Each component is a TOML table, and the name is written as part of the table header. For example:

[component.greeter]

The Component source

Each component has a source field which tells Spin where to load the Wasm module from.

For components that you are working on locally, set it to the file path to the compiled Wasm module file. This must be a relative path from the directory containing spin.toml.

[component.shopping-cart]
source = "dist/cart.wasm"

For components that are published on the Web, provide a url field containing the URL of the Wasm module, and a digest field indicating the expected SHA256 hash of the module contents. The digest must be prefixed with the string sha256:. Spin uses the digest to check that the module is what you were expecting, and won’t load the module if the content doesn’t match.

[component.asset-server]
source = { url = "https://github.com/fermyon/spin-fileserver/releases/download/v0.0.1/spin_static_fs.wasm", digest = "sha256:650376c33a0756b1a52cad7ca670f1126391b79050df0321407da9c741d32375" }

Multiple components can have the same source. An example is a document archive, where one component might serve user interface assets (CSS, images, etc.) on one route, while another serves the documents themselves on another route - both using the same file server module, but with different settings.

Writing a Component Wasm Module

You won’t normally create a component by hand, but will instead create or add one from a template. Skip to those sections if that’s what you want to do. But it’s important to know what a component looks like so you can understand where to write your own code.

At the Wasm level, a Spin component is a Wasm component or module that exports a handler for the application trigger. At the developer level, 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 handled by Spin.

See the Language Guides section for how to do this in your preferred language. As an example, this is a component written in the Rust language. The hello_world function uses an attribute #[http_component] to identify the function as handling a Spin HTTP event. The function takes a Request and returns a anyhow::Result<impl IntoResponse>.

use spin_sdk::http::{IntoResponse, Request, Response};
use spin_sdk::http_component;

/// A simple Spin HTTP component.
#[http_component]
fn hello_world(req: Request) -> anyhow::Result<impl IntoResponse> {
    println!("Handling request to {:?}", req.header("spin-full-url"));
    Ok(Response::builder()
        .status(200)
        .header("content-type", "text/plain")
        .body("Hello, Fermyon")
        .build())
}​​

Creating an Application From a Template

If you’ve installed the Spin templates for your preferred language, you can use them to get started without having to write a manifest or the boilerplate code yourself. To do this, run the spin new command, and choose the template that matches the type of application you want to create, and the language you want to use.

Choose the http-rust template to create a new HTTP application, or redis-rust to create a new Redis application.

$ spin new
Pick a template to start your application with:
  http-c (HTTP request handler using C and the Zig toolchain)
  http-csharp (HTTP request handler using C# (EXPERIMENTAL))
  http-go (HTTP request handler using (Tiny)Go)
  http-grain (HTTP request handler using Grain)
> http-rust (HTTP request handler using Rust)
  http-swift (HTTP request handler using SwiftWasm)
  http-zig (HTTP request handler using Zig)
  redis-go (Redis message handler using (Tiny)Go)
  redis-rust (Redis message handler using Rust)

Enter a name for your new application: hello_rust
Project description: My first Rust Spin application
HTTP path: /...

Choose the http-ts or http-js template to create a new HTTP application, according to whether you want to use TypeScript or JavaScript.

The JavaScript development kit doesn’t yet support Redis applications.

$ spin new
Pick a template to start your application with:
  http-js (HTTP request handler using Javascript)
> http-ts (HTTP request handler using Typescript)
Enter a name for your new application: hello_typescript
Project description: My first TypeScript Spin application
HTTP path: /...

Choose the http-py template to create a new HTTP application.

The Python development kit doesn’t yet support Redis applications.

$ spin new
Pick a template to start your application with:
> http-py (HTTP request handler using Python)
Enter a name for your new application: hello_python
Description: My first Python Spin application
HTTP path: /...

Choose the http-go template to create a HTTP application, or redis-go to create a Redis application.

$ spin new
Pick a template to start your application with:
  http-c (HTTP request handler using C and the Zig toolchain)
  http-empty (HTTP application with no components)
> http-go (HTTP request handler using (Tiny)Go)
  http-grain (HTTP request handler using Grain)
  http-php (HTTP request handler using PHP)
  http-rust (HTTP request handler using Rust)
Enter a name for your new application: hello_go
Description: My first Go Spin application
HTTP path: /...

All of these templates create a manifest containing a single component, and the source code for a minimal “hello world” component.

Adding a New Component to an Application

To add a new component to an existing application using a template, run the spin add command. This works in a very similar way to spin new, except that it expects the spin.toml file to already exist, and adds the details for the new component to that spin.toml.

Please take a look at the Spin application structure documentation, which explains how to achieve the recommended application structure (through the use of Spin templates via the spin new and spin add commands).

Including Files with Components

You can include files with a component. This means that:

  1. The Wasm module will be able to read those files at runtime.
  2. When you distribute or deploy the application, those files will be bundled with it so that the component can still access them.

To do this, use the files field in the component manifest:

[component.asset-server]
files = [ "images/**/*.jpg", { source = "styles/dist", destination = "/styles" } ]

The files field is an array listing the files, patterns and directories you want to include. Each element of the array can be:

  • A glob pattern, such as images/**/*.jpg, or single file path. In this case, the file or files that match the pattern are available to the Wasm code, at the same paths as they are in your file system. For example, if the glob pattern matches images/photos/lake.jpg, the Wasm module can access it using the path images/photos/lake.jpg. Glob patterns are relative to the directory containing spin.toml, and must be within that directory.
  • A mapping from a source file or directory to a destination location, such as { source = "styles/dist", destination = "/styles" }. In this case, the file, or the entire contents of the source directory, are available to the Wasm code at the destination location. In this example, if you have a file named styles/dist/list/exciting.css, the Wasm module can access it using the path /styles/list/exciting.css. Source locations are relative to the directory containing spin.toml; destination locations must be absolute.

If your files list would match some files or directories that you don’t want included, you can use the exclude_files field to omit them.

By default, Spin takes a snapshot of your included files, and components access that snapshot. This ensures that when you test your application, you are checking it with the set of files it will be deployed with. However, it also means that your component does not pick up changes to the files while it is running. When testing a Web site, you might want to be able to edit a HTML or CSS file, and see the changes reflected without restarting Spin. You can tell Spin to give components access to the original, “live” files by passing the --direct-mounts flag to spin up.

Each component can access only the files included via its files section. It does not have access to files included by other components, to your source code, or to the compiled .wasm file (unless you add those to the files section).

Adding Environment Variables to Components

Environment variables can be provided to components via the Spin application manifest.

To do this, use the environment field in the component manifest:

[component.pet-info]
environment = { PET = "CAT", FOOD = "WATERMELON" }

The field accepts a map of environment variable key/value pairs. They are mapped inside the component at runtime.

The environment variables can then be accessed inside the component. For example, in Rust:

use spin_sdk::http::{IntoResponse, Request, Response};
use spin_sdk::http_component;

#[http_component]
fn handle_hello_rust(req: Request) -> anyhow::Result<impl IntoResponse> {
    let response = format!("My {} likes to eat {}", std::env::var("PET")?, std::env::var("FOOD")?);
    Ok(Response::builder()
        .status(200)
        .header("content-type", "text/plain")
        .body(response)
        .build())
}

Granting Networking Permissions to Components

By default, Spin components are not allowed to make outgoing network requests. This follows the general Wasm rule that modules must be explicitly granted capabilities, which is important to sandboxing.

To grant a component permission to make outbound requests to a particular address, use the allowed_outbound_hosts field in the component manifest. Each entry must comprise a host name or IP address and a port. For example:

[component.talkative]
allowed_outbound_hosts = ["redis://redis.example.com:6379", "https://api.example.com:8080"]

If a port is specified, the component can make requests only to that port. If no port is specified, the component can make requests only to the default port for the scheme (e.g. port 443 for the https scheme, port 5432 for the postgres scheme). If you need to allow requests to any port, use the wildcard * (e.g. mysql://db.example.com:*).

If you’re familiar with Spin 1, allowed_outbound_hosts replaces allowed_http_hosts. Spin 2 still accepts allowed_http_hosts but will recommend migrating to allowed_outbound_hosts. Additionally, if your application uses the version 1 manifest format, and the manifest does not specify allowed_outbound_hosts, then version 1 components are allowed to use Redis, MySQL and PostgreSQL without restriction.

The Wasm module can send network traffic only to the hosts specified in these two fields. Requests to other hosts (or other ports) will fail with an error.

This feels like extra work! Why do I have to list the hosts?

This comes from the Wasm principle of deny by default: the user of a component, rather than the component itself, should decide what resource it’s allowed to access. But this isn’t just an abstract principle: it’s critical to being able to trust third party components. For example, suppose you add bad-boy-brians-totally-legitimate-file-server.wasm to your application. Unless you unwisely grant it network permissions, you can be provably certain that it doesn’t access your Postgres database or send information to evildoers.

For development-time convenience, you can also pass the string "*://*:*" in the allowed_outbound_hosts collection. This allows the component to make network requests to any host and on any port. However, once you’ve determined which hosts your code needs, you should remove this string, and list the hosts instead. Other Spin implementations may restrict host access, and may disallow components that ask to connect to anything and everything!

Granting Storage Permissions to Components

By default, Spin components are not allowed to access Spin’s storage services. This follows the general Wasm rule that modules must be explicitly granted capabilities, which is important to sandboxing. To grant a component permission to use a Spin-provided store, use the key_value_stores field in the component manifest:

[component.squirrel]
key_value_stores = [ "default" ]

See Persisting Data with Spin for more information.

Example Manifests

This section shows some examples of Spin component manifests.

Including a Directory of Files

This example shows a Spin HTTP component that includes all the files in static/ under the application directory, made available to the Wasm module at the / path.

[[trigger.http]]
route = "/static/..."
component = "fileserver"

[component.fileserver]
source = "modules/spin_static_fs.wasm"
files = [ { source = "static/", destination = "/" } ]

Using Public Components

This is similar to the file server component above, but gets the Wasm module from a public release instead of a local copy. Notice that the files are still drawn from a local path. This is a way in which you can use off-the-shelf component logic with your own application data.

[[trigger.http]]
route = "/static/..."
component = "fileserver"

[component.fileserver]
source = { url = "https://github.com/fermyon/spin-fileserver/releases/download/v0.0.1/spin_static_fs.wasm", digest = "sha256:650376c33a0756b1a52cad7ca670f1126391b79050df0321407da9c741d32375" }
files = [ { source = "static/", destination = "/" } ]

Customizing the Executor

This example shows an HTTP component whose Wasm module, instead of using the default Spin HTTP interface, uses the CGI-like WAGI (WebAssembly Gateway Interface) protocol. Spin can’t work out the application model from the component, so the manifest needs to tell Spin to use WAGI instead of its default mode. It does this via the executor field. This is specific to the HTTP trigger so it goes under trigger.http.

In addition, this module does not provide its WAGI functionality via the default _start entry point, but via a custom entry point named serve-wagi. The executor table needs to tell Spin this via the entrypoint field. Finally, this component needs to run the WAGI entry point with a specific set of command line arguments, which are expressed in the WAGI executor’s argv field.

[[trigger.http]]
route = "/..."
component = "env"
executor = { type = "wagi", argv = "test ${SCRIPT_NAME} ${ARGS} done", entrypoint = "serve-wagi" }

[component.env]
source = "modules/env_wagi.wasm"
files = [ "content/**/*" , "templates/*", "scripts/*", "config/*"]

Setting the Redis Channel to Monitor

The example shows a Redis component. The manifest sets the channel field to say which channel the component should monitor. In this case, the component is invoked for new messages on the messages channel.

[[trigger.redis]]
channel = "messages"
component = "echo-message"

[component.echo-message]
source = "spinredis.wasm"

Using Component Dependencies

Few of us write applications without relying on libraries. Traditionally, those libraries have had to come from the language ecosystem - e.g. npm for JavaScript, pip for Python, etc. - and you can still work this way in Spin. However, the WebAssembly Component Model means that you can also depend on other WebAssembly components. The process of combining your application component with the Wasm components it depends on is called composition, and Spin supports this natively.

Spin’s composition is limited to “plug” style scenarios where each of your component’s imports is satisfied independently, and where the dependency component does not need to be further composed with any other components. The analogy is plugging each of your imports into a socket provided by a dependency. If you need to construct a more complex composition, you must use a dedicated tool such as wac as part of your build. See the Component Model book for details and examples.

To use composition through Spin, your component must import a WIT (Wasm Interface Type) interface, and the dependency must export the same WIT interface. The details of working with WIT interfaces is language-specific, and is beyond the scope of the Spin documentation. You can learn more from the language guides in the Component Model book. This section focuses on describing the dependency composition support in Spin.

Component dependencies are not currently supported on Fermyon Cloud.

Declaring Component Dependencies

To declare a component dependency, create a [component.(name).dependencies] table in your Spin manifest, and list all the WIT interfaces you import (other than the ones that Spin itself satisfies), together with the packages that you would like to use to satisfy those imports.

For example, suppose your component imports a WIT interface named security:http/malice for detecting malicious HTTP requests. This interface might be defined by a vendor or standards body, and might have multiple implementations. Suppose Bargain Security, Inc. provides an HTTP inspection package which includes an implementation of this interface, and that they publish this in the registry.example.com registry as bargains:inspection@2.0.0. You can then set up your dependency as follows:

[component.my-app.dependencies]
"security:http/malice" = { package = "bargains:inspection", version = "2.0.0", registry = "packages.example.com" }

During loading, Spin will download the package from the registry, locate its security:http/malice export, and wire up your imports to that export so that when your component calls a function in the WIT interface, that call is dispatched to the Bargain Security package.

Your Wasm component depends only on the WIT interface. If, inexplicably, you become dissatisfied with Bargain Security, Inc., then you can switch to a different vendor by changing the package reference in the dependency mapping (and, of course, re-testing with the new implementation).

Specifying Dependencies

Spin supports three sources for dependencies.

Dependencies from a Registry

To use a dependency from a registry, specify the following fields:

FieldRequired?DescriptionExample
versionRequiredA semantic versioning constraint for the package version to use.">= 1.1.0"
packageOptionalThe name of the package to use. If omitted, this defaults to the package name of the imported interface."bargains:inspection"
registryOptionalThe registry that hosts the package. If omitted, this defaults to your system default registry."registry.example.com"
exportOptionalThe name of the export in the package. If omitted, this defaults to the name of the import."more-secure:checking-it-out/web"

If you don’t need any of the optional fields, you can provide the version constraint as a plain string instead of writing out the table:

# Use the `security:http` package in the default registry
"security:http/malice" = "2.0.0"

Dependencies from a Local Component

To use a dependency from a component on your file system, specify the following fields:

FieldRequired?DescriptionExample
pathRequiredThe path to the Wasm file containing the component."../validation/request-checker.wasm"
exportOptionalThe name of the export in the package. If omitted, this defaults to the name of the import."more-secure:checking-it-out/web"

Dependencies from a URL

To use a dependency from an HTTP URL, such as a GitHub release, specify the following fields:

FieldRequired?DescriptionExample
urlRequiredThe URL of the Wasm file containing the component."https://example.com/downloads/request-checker.wasm"
digestRequiredThe SHA256 digest of the Wasm file. This is required for integrity checking."sha256:650376c33a0756b1a52cad7ca670f1126391b79050df0321407da9c741d32375"
exportOptionalThe name of the export in the package. If omitted, this defaults to the name of the import."more-secure:checking-it-out/web"

Mapping All Imports from a Package

If you are importing several interfaces from the same WIT package, and want them all satisfied by the same Wasm package, you can omit the interface from the dependency name. For example, suppose you import the malice, tomfoolery, and shenanigans WIT interfaces from the security:http package, and that your Bargain Security package exports all three of them. You can write:

[component.my-app.dependencies]
"security:http" = { package = "bargains:inspection", version = "2.0.0", registry = "packages.example.com" }

and Spin will map all of your security:http imports to the matching exports from the package.

Dependency Permissions

By default, dependencies do not have access to Spin resources that require permission to be given in the manifest - network hosts, key-value stores, SQLite databases, variables, etc.

If a component has a dependency which requires resource access, you can grant it by setting the dependencies_inherit_configuration flag in the Spin component manifest:

[component.my-app]
dependencies_inherit_configuration = true

This grants all dependencies access to all resources listed in the Spin component manifest. You should therefore set this only if you trust all dependencies.

Spin does not currently support inheritance on a dependency-by-dependency or feature-by-feature basis.

Next Steps