Quickstart
Before we begin
Looking for an introduction to the basic concepts of WebAssembly components and wasmCloud? Check out What is wasmCloud? or Platform Overview first.
In this guide, we'll take a tour through the wasmCloud ecosystem with a "Hello world" example. Once you're finished, you'll have a good understanding of how to use wasmCloud Shell (wash
) to build and deploy wasmCloud applications.
Start the wasmCloud host
wash
)If you haven't installed wash
yet, follow the installation guide first.
All wasmCloud applications need a host to run on, so let's use wash
to start one now. You can start a wasmCloud host on your local machine with wash up
or run a host in a Docker container.
- Local
- Docker
To start a host, simply run:
wash up
The default settings should work well for this tutorial. The host can be killed at any time with CTRL+c
.
Initially, you'll have to clone the wasmCloud repository. Then, once you have the repository locally, you'll have to cd
into the directory wasmCloud/examples/docker/
.
There, you'll find many examples to run with docker compose
.
If you want to install the full wasmCloud host infrastructure, simply run:
docker compose --file docker-compose-full.yml up
This command bootstraps three independent processes to create the necessary local infrastructure for wasmCloud:
- the wasmCloud host which manages the execution of components and providers
- a NATS server to manage communications between application components
- a wadm process which monitors the lattice and maintains the state of managed deployments
By default, wash
will run the host in interactive mode.
If you'd rather run the host in the background, run wash up --detached
(or wash up -d
, for short).
Create a project
Now that our wasmCloud host infrastructure is running, let's generate a new component project. This is where our "Hello World" example begins. If you're running your host in the foreground, just switch to another terminal window to run these commands.
Pick your language of choice below to follow
- Rust
- TinyGo
- TypeScript
- Python
- My Language Isn't Listed
This command generates a new component project in the ./hello
directory:
wash new component hello --template-name hello-world-rust
This component is a simple Rust project with a few extra goodies included for wasmCloud. At a high level, the important pieces are:
src/lib.rs
: Where the business logic is implementedwasmcloud.toml
: Component metadata and build informationwadm.yaml
: A declarative manifest for running the full application
You don't need any Rust dependencies installed to run this example, but if you want to build it yourself you'll need to install the Rust toolchain and the wasm32-wasi
target.
rustup target add wasm32-wasi
This command generates a new component project in the ./hello
directory:
wash new component hello --template-name hello-world-tinygo
This component is a simple Go project with a few extra goodies included for wasmCloud. At a high level, the important pieces are:
hello.go
: Where the business logic is implementedwasmcloud.toml
: Component metadata and build informationwadm.yaml
: A declarative manifest for running the full application
This command generates a new component project in the ./hello
directory:
wash new component hello --template-name hello-world-typescript
This component is a simple TypeScript project with a few extra goodies included for wasmCloud. At a high level, the important pieces are:
index.ts
: Where the business logic is implementedwasmcloud.toml
: Component metadata and build informationwadm.yaml
: A declarative manifest for running the full application
You don't need any TypeScript/JavaScript dependencies installed to run this example, but if you want to build it yourself you'll need to install npm and run npm install
in the project directory. We use npm to install dependencies and compile the TS/JS code to WebAssembly.
This command generates a new component project in the ./hello
directory:
wash new component hello --template-name hello-world-python
This component is a simple Python project with a few extra goodies included for wasmCloud. At a high level, the important pieces are:
app.py
: Where the business logic is implementedwasmcloud.toml
: Component metadata and build informationwadm.yaml
: A declarative manifest for running the full application
You don't need any Python dependencies installed to run this example, but if you want to build it yourself you'll need to install Python 3.10 or later and pip. After you have those, you'll need to install componentize-py v0.12 or later:
pip install componentize-py
If you prefer working in a language that isn't listed here, let us know!
Feel free to take a look at the code and the project structure. We'll take a look at each of these in depth later, so for now let's build and run the example.
Build the application
If you prefer not to build anything yet, you can use ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0
as the image inside of wadm.yaml
instead to pull our prebuilt example component.
We can use the open source httpserver provider to serve our application, so the only thing you'll need to build is the component. Change directory into the generated hello
directory and run wash build
to build your component:
cd hello # enter the new project directory (if you haven't already)
wash build
Component built and signed and can be found at "/Users/wasmcloud/hello/build/http_hello_world_s.wasm"
Start our application
We'll use wash
and wadm, the wasmCloud application deployment manager, to start this component and the httpserver provider, and link them together to configure them.
Then, we can deploy our application:
cd hello # enter the new project directory (if you haven't already)
wash app deploy wadm.yaml
wadm
will take care of taking this manifest and scheduling the resources on the host that you launched earlier. You should see output in the host logs when your component and provider start up, which should happen after just a few seconds. After you see that the HTTP server set up the listener in the host logs, or once the application is Deployed when you check wash app list
, you can query it yourself:
➜ wash app list
Name Latest Version Deployed Version Deploy Status Description
http-hello-world v0.0.1 v0.0.1 Deployed HTTP Hello World
$ curl localhost:8080
Hello, World!
Congratulations, you just ran a WebAssembly application in wasmCloud!
How does it work?
When you send your HTTP request to localhost:8080
, that request is received by the httpserver provider. The httpserver provider then forwards that request to the component, which responds with the string "Hello, world!". This loose coupling of providers and components provides flexibility and security, and it lets our component code focus purely on business logic rather than including and compiling vendor-specific code. Let's take a look at your application code now:
- Rust
- TinyGo
- TypeScript
- Python
wit_bindgen::generate!({
world: "hello",
});
use exports::wasi::http::incoming_handler::Guest;
use wasi::http::types::*;
struct HttpServer;
impl Guest for HttpServer {
fn handle(_request: IncomingRequest, response_out: ResponseOutparam) {
let response = OutgoingResponse::new(Fields::new());
response.set_status_code(200).unwrap();
let response_body = response.body().unwrap();
response_body
.write()
.unwrap()
.blocking_write_and_flush(b"Hello from Rust!\n")
.unwrap();
OutgoingBody::finish(response_body, None).expect("failed to finish response body");
ResponseOutparam::set(response_out, Ok(response));
}
}
export!(HttpServer);
This component has a single struct, HttpServer
, that implements the handle
function. This function is called by the httpserver provider when it receives an incoming HTTP request. The handle
function constructs an HttpResponse
and sends it back to the httpserver provider, which then sends it back to the client. There's no mention of ports, certificates, HTTP libraries, and if those things change this component will work all the same.
package main
import (
http "github.com/wasmcloud/wasmcloud/examples/golang/components/http-hello-world/gen"
)
// Helper type aliases to make code more readable
type HttpRequest = http.ExportsWasiHttp0_2_0_IncomingHandlerIncomingRequest
type HttpResponseWriter = http.ExportsWasiHttp0_2_0_IncomingHandlerResponseOutparam
type HttpOutgoingResponse = http.WasiHttp0_2_0_TypesOutgoingResponse
type HttpError = http.WasiHttp0_2_0_TypesErrorCode
type HttpServer struct{}
func init() {
httpserver := HttpServer{}
// Set the incoming handler struct to HttpServer
http.SetExportsWasiHttp0_2_0_IncomingHandler(httpserver)
}
func (h HttpServer) Handle(request HttpRequest, responseWriter HttpResponseWriter) {
// Construct HttpResponse to send back
headers := http.NewFields()
httpResponse := http.NewOutgoingResponse(headers)
httpResponse.SetStatusCode(200)
body := httpResponse.Body().Unwrap()
bodyWrite := body.Write().Unwrap()
bodyWrite.BlockingWriteAndFlush([]uint8("Hello from Go!\n")).Unwrap()
// Send HTTP response
okResponse := http.Ok[HttpOutgoingResponse, HttpError](httpResponse)
bodyWrite.Drop()
http.StaticOutgoingBodyFinish(body, http.None[http.WasiHttp0_2_0_TypesTrailers]())
http.StaticResponseOutparamSet(responseWriter, okResponse)
}
//go:generate wit-bindgen tiny-go wit --out-dir=gen --gofmt
func main() {}
This component has a single struct, HttpServer
, that implements the Handle
function. This function is called by the httpserver provider when it receives an incoming HTTP request. The Handle
function constructs an HttpResponse
and sends it back to the httpserver provider, which then sends it back to the client. There's no mention of ports, certificates, HTTP libraries, and if those things change this component will work all the same.
import { IncomingRequest, ResponseOutparam, OutgoingResponse, Fields } from 'wasi:http/types@0.2.0';
// Implementation of wasi-http incoming-handler
//
// NOTE: To understand the types involved, take a look at wit/deps/http/types.wit
function handle(req: IncomingRequest, resp: ResponseOutparam) {
// Start building an outgoing response
const outgoingResponse = new OutgoingResponse(new Fields());
// Access the outgoing response body
let outgoingBody = outgoingResponse.body();
{
// Create a stream for the response body
let outputStream = outgoingBody.write();
// Write hello world to the response stream
outputStream.blockingWriteAndFlush(
new Uint8Array(new TextEncoder().encode('Hello from Typescript!\n')),
);
// @ts-ignore: This is required in order to dispose the stream before we return
outputStream[Symbol.dispose]();
}
// Set the status code for the response
outgoingResponse.setStatusCode(200);
// Finish the response body
OutgoingBody.finish(outgoingBody, undefined);
// Set the created response
ResponseOutparam.set(resp, { tag: 'ok', val: outgoingResponse });
}
export const incomingHandler = {
handle,
};
This component has a single function, handle
, that implements the incoming-handler
interface. This function is called by the httpserver provider when it receives an incoming HTTP request. The handle
function constructs an OutgoingResponse
and sends it back to the httpserver provider, which then sends it back to the client. There's no mention of ports, certificates, HTTP libraries, and if those things change this component will work all the same.
from hello import exports
from hello.types import Ok
from hello.imports.types import (
IncomingRequest, ResponseOutparam,
OutgoingResponse, Fields, OutgoingBody
)
class IncomingHandler(exports.IncomingHandler):
def handle(self, _: IncomingRequest, response_out: ResponseOutparam):
# Construct the HTTP response to send back
outgoingResponse = OutgoingResponse(Fields.from_list([]))
# Set the status code to OK
outgoingResponse.set_status_code(200)
outgoingBody = outgoingResponse.body()
# Write our Hello World message to the response body
outgoingBody.write().blocking_write_and_flush(bytes("Hello from Python!\n", "utf-8"))
OutgoingBody.finish(outgoingBody, None)
# Set and send the HTTP response
ResponseOutparam.set(response_out, Ok(outgoingResponse))
This component has a single class, IncomingHandler
, that implements the handle
function. This function is called by the httpserver provider when it receives an incoming HTTP request. The handle
function constructs an HttpResponse and sends it back to the httpserver provider, which then sends it back to the client. There's no mention of ports, certificates, HTTP libraries, and if those things change this component will work all the same.
Capabilities are deny by default, the link inside of wadm.yaml
explicitly allowed the "Hello World" component to receive an HTTP request. If you inspect the WIT output of your component with wash inspect --wit build/http_hello_world.wasm
you'll see the only export
is wasi:http/incoming-handler
, and the only import
s are standard capabilities. This means that this component cannot make network requests of its own, read arbitrary files, or even generate a random number, all are denied by the wasmCloud runtime.
When you ran wash app deploy wadm.yaml
, wadm
received the application manifest and scheduled the components on hosts as specified in your application. This example has a minimal manifest, let's take a look at it:
# Metadata
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: hello-world
annotations:
description: 'HTTP hello world demo'
spec:
components:
- name: http-component
type: component
properties:
# Your manifest deploys from a local .wasm file, but you can also use OCR registries like below
image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0
traits:
# One replica of this component will run
- type: spreadscaler
properties:
replicas: 1
# The httpserver capability provider, started from the official wasmCloud OCI artifact
- name: httpserver
type: capability
properties:
image: ghcr.io/wasmcloud/http-server:0.22.0
traits:
# Link the HTTP server and set it to listen on the local machine's port 8080
- type: link
properties:
target: http-component
namespace: wasi
package: http
interfaces: [incoming-handler]
source_config:
- name: default-http
properties:
ADDRESS: 127.0.0.1:8080
View the wasmCloud dashboard
To view the wasmCloud dashboard, you can start it using the wash
.
wash ui
If you would like to change the port that the websocket is enabled on, you can pass the --nats-websocket-port
option to wash up
.
Note that you will need to stop NATS if it is already running or the new config will not apply. You can do this with wash down --all
to stop everything.
wash up --nats-websocket-port 4444 # defaults to 4223
Treat wasmCloud hosts as commodity. Stopping your host will stop all of the components running on it, and wadm will look for other available hosts to reschedule your application on. As soon as you launch your new host with the --nats-websocket-port
option, your application will be rescheduled on it and work just as it did before.
Then, you can launch the wasmCloud dashboard using wash ui
, which will launch the dashboard on http://localhost:3030.
This is a great way to visualize what is running on your host, even multiple hosts that are connected to the same NATS server.
Log files
If you encounter any problems, the host log files may contain useful error messages, and it's good to know how to find them. The tabs below, organized by how you started the wasmCloud host, show you where to find logs:
- Local
- Docker
By default, logs from wash up
are automatically output to your terminal. If you ran the command
with the --detached
flag, logs can be found in ~/.wash/downloads/wasmcloud.log
Logs from hosts running in Docker, if started with our docker compose, can be found by running
docker logs wasmcloud
.
Next steps
Congratulations! You've made it through the first guide to wasmCloud. You started your first application and got familiar with the fundamentals of wash
and wadm
. You should now feel
comfortable exploring the ecosystem. We recommend proceeding onto the next guide, where you can build your own application and get into the developer loop.