Develop a Wasm Component
Once you know what your application needs to be able to do, you can add language-agnostic interfaces for common capabilities like HTTP, key-value storage, or logging.
In this step, we'll discuss how to:
- Update our application to accept a name and return a personalized greeting.
- Add more features to our application by plugging in key-value and logging capabilities.
This tutorial assumes you're following directly from the previous tutorial. If you don't have a "Hello world" application running, complete Quickstart first.
Add functionality
- Rust
- TypeScript
- Go
- My language isn't listed
Let's extend this application to do more than just say "Hello!"
We can check the request for a name provided in a query string, and then return a greeting with that name. If there isn't one or the path isn't in the format we expect, we'll default to saying "Hello, World!"
use wstd::http::{Body, Request, Response, StatusCode};
#[wstd::http_server]
async fn main(req: Request<Body>) -> Result<Response<Body>, wstd::http::Error> {
match req.uri().path_and_query().unwrap().as_str() {
"/" => home(req).await,
_ => not_found(req).await,
}
}
async fn home(req: Request<Body>) -> Result<Response<Body>, wstd::http::Error> {
// Parse query string for name parameter
let query = req.uri().query().unwrap_or("");
let name = match query.split("=").collect::<Vec<&str>>()[..] {
["name", name] => name,
_ => "World",
};
// Return a simple response with a string body
Ok(Response::new("Hello from Wasm!\n".into()))
Ok(Response::new(format!("Hello, {name}!\n").into()))
}
async fn not_found(_req: Request<Body>) -> Result<Response<Body>, wstd::http::Error> {
Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body("Not found\n".into())
.unwrap())
}Let's extend this application to do more than just say "Hello!"
Using Hono's built-in query parameter parsing, we can check the request for a name provided in a query string, and then return a greeting with that name. If there isn't one, we'll default to saying "Hello, World!"
import { Hono } from 'hono';
import { fire } from '@bytecodealliance/jco-std/wasi/0.2.x/http/adapters/hono/server';
const app = new Hono();
app.get('/', (c) => {
return c.text('Hello from Wasm!\n');
const name = c.req.query('name') || 'World';
return c.text(`Hello, ${name}!\n`);
});
app.notFound((c) => {
return c.text('Not found\n', 404);
});
fire(app);
export { incomingHandler } from '@bytecodealliance/jco-std/wasi/0.2.x/http/adapters/hono/server';These instructions are in progress and will be available soon.
If you prefer working in a language that isn't listed here, let us know!
After saving your changes, your toolchain automatically builds and runs the updated application.
We can curl the application again:
curl localhost:8000Hello, World!curl 'localhost:8000?name=Bob'Hello, Bob!Add persistent storage
Now let's add persistent storage to keep a record of each person that this application greeted.
We'll use the key-value capability for this. We don't need to pick a library or a specific vendor implementation—all we have to do is add the interface to our component.
We can use the wasi:keyvalue interface for interacting with a key value store.
- Rust
- TypeScript
- Go
- My language isn't listed
Before we can use the interface, we'll need to add a wit/world.wit file:
touch wit/world.witpackage wasmcloud:templates@0.1.0;
world rust-http-hello-world {
import wasi:keyvalue/store@0.2.0-draft;
import wasi:keyvalue/atomics@0.2.0-draft;
}Before we can use the interface, we'll need to update the wit/world.wit file:
package wasmcloud:templates@0.1.0;
world typescript-http-hello-world-hono {
import wasi:keyvalue/store@0.2.0-draft;
import wasi:keyvalue/atomics@0.2.0-draft;
export wasi:http/incoming-handler@0.2.6;
}These instructions are in progress and will be available soon.
If you prefer working in a language that isn't listed here, let us know!
- Rust
- TypeScript
- Go
- My language isn't listed
We've given our application the ability to perform atomic incrementation and storage operations via the wasi:keyvalue interface.
Now we need to add wit-bindgen to our Cargo.toml so we can generate Rust bindings from the WIT world:
[dependencies]
wstd = "0.6.3"
wit-bindgen = "0.46.0"Now let's use the atomic increment function to keep track of how many times we've greeted each person.
use wstd::http::{Body, Request, Response, StatusCode};
// Generate keyvalue bindings from our WIT world
// wstd handles the http export via its macro
wit_bindgen::generate!({
world: "rust-http-hello-world",
path: "wit",
generate_all,
});
use wasi::keyvalue::{atomics, store};
#[wstd::http_server]
async fn main(req: Request<Body>) -> Result<Response<Body>, wstd::http::Error> {
match req.uri().path_and_query().unwrap().as_str() {
"/" => home(req).await,
_ => not_found(req).await,
}
}
async fn home(req: Request<Body>) -> Result<Response<Body>, wstd::http::Error> {
// Parse query string for name parameter
let query = req.uri().query().unwrap_or("");
let name = match query.split("=").collect::<Vec<&str>>()[..] {
["name", name] => name,
_ => "World",
};
// Open keyvalue bucket and increment counter for this name
// Note: wasmtime's in-memory keyvalue provider requires empty string as identifier
let bucket = store::open("")
.map_err(|e| wstd::http::Error::msg(format!("keyvalue open error: {:?}", e)))?;
let count = atomics::increment(&bucket, &name, 1)
.map_err(|e| wstd::http::Error::msg(format!("keyvalue increment error: {:?}", e)))?;
// Return greeting with count
Ok(Response::new(format!("Hello x{count}, {name}!\n").into()))
// Return a simple response with a string body
Ok(Response::new(format!("Hello, {name}!\n").into()))
}
async fn not_found(_req: Request<Body>) -> Result<Response<Body>, wstd::http::Error> {
Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body("Not found\n".into())
.unwrap())
}We've made changes, so once we save, the toolchain will once again automatically update the running application.
We've given our application the ability to perform atomic incrementation and storage operations via the wasi:keyvalue interface.
Now let's use the atomic increment function to keep track of how many times we've greeted each person.
At the time of writing, JCO does not generate types for wasi:keyvalue. This is
a known issue and will be resolved in a future release. For now, you can tell the TypeScript
compiler to ignore the missing types by adding //@ts-expect-error before each import statement.
Simply including the import statement will allow the host to provide the functionality at runtime.
import { Hono } from 'hono';
import { fire } from '@bytecodealliance/jco-std/wasi/0.2.x/http/adapters/hono/server';
//@ts-expect-error -- these types aren't currently generated by JCO
import { increment } from 'wasi:keyvalue/atomics@0.2.0-draft';
//@ts-expect-error -- these types aren't currently generated by JCO
import { open } from 'wasi:keyvalue/store@0.2.0-draft';
const app = new Hono();
app.get('/', (c) => {
const name = c.req.query('name') || 'World';
return c.text(`Hello, ${name}!\n`);
// Open keyvalue bucket and increment counter for this name
const bucket = open('');
const count = increment(bucket, name, 1);
return c.text(`Hello x${count}, ${name}!\n`);
});
app.notFound((c) => {
return c.text('Not found\n', 404);
});
fire(app);
export { incomingHandler } from '@bytecodealliance/jco-std/wasi/0.2.x/http/adapters/hono/server';We've made changes, so once we save, the toolchain will once again automatically update the running application.
These instructions are in progress and will be available soon.
If you prefer working in a language that isn't listed here, let us know!
Let's curl the component again:
curl 'localhost:8000?name=Bob'Hello x1, Bob!curl 'localhost:8000?name=Bob'Hello x2, Bob!curl 'localhost:8000?name=Alice'Hello x1, Alice!Next steps
In this tutorial, you added a few more features and persistent storage to a simple microservice. You also got to see the process of developing with capabilities, where you can...
- Write purely functional code that doesn't require you to pick a library or vendor upfront
- Change your application separately from its non-functional requirements
So far, wash has satisfied our application's capability requirements automatically, so we can move quickly and focus on code. In the next tutorial, we'll deploy WebAssembly workloads to wasmCloud on a local Kubernetes cluster.