Create
We provide example templates for creating components in Rust, TinyGo, Python, and TypeScript. You can use these templates to create a new component project, or you can use them as a reference for creating your own component from scratch. As other languages are updated to use the same component APIs that wasmtime uses, we will add templates for those languages as well.
- Rust
- TinyGo
- TypeScript
- Python
- My Language Isn't Listed
Creating the scaffold for a new component in Rust is easy. We will create a component that accepts an HTTP request and responds with "Hello from Rust!". To create your new component project, change to the directory where you want the project to be created, and enter the command below. The first term on the command (hello
) is the project name. If you choose a different project name, the name of the subdirectory and some symbols in the generated code will be different from the example code in this guide.
wash new component hello --template-name hello-world-rust
Let's change into the newly-created folder hello
and take a look at the generated project. The file src/lib.rs
includes a wit-bindgen
generate macro, an imports section, a struct HttpServer
, and an impl
block that implements the Guest
trait for the component struct. Let's walk through these sections in detail.
wit_bindgen::generate!();
This shows us that we're using a core wasmCloud package called wit-bindgen
to generate the types and bindings for our component. This is automatically done from the wit/world.wit
file, any changes to imports or exports will update automatically on the next build.
Note the two lines near the top of the source code file:
use exports::wasi::http::incoming_handler::Guest;
use wasi::http::types::*;
These two imports are bringing in the Guest
trait to implement our function to be called whenever an incoming HTTP request is received, and the types that we'll use to interact with the HTTP request and response. These are the only two imports that we need to interact with the HTTP server capability.
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));
}
}
Within the handle
method, the component receives the HTTP request, creates an OutgoingResponse
and writes that response out back to the requesting http client (such as a curl
command or a web browser).
Finally, we use the wit-bindgen
generated macro export!
to export the HttpServer
struct as the incoming handler for the HTTP server capability. If you don't implement the Guest
trait for your struct, the export!
macro will give you an error at compile time noting which functions you need to implement.
export!(HttpServer);
Creating the scaffold for a new component in TinyGo is easy. We will create a component that accepts an HTTP request and responds with "Hello from Go!". To create your new component project, change to the directory where you want the project to be created, and enter the command below. The first term on the command (hello
) is the project name. If you choose a different project name, the name of the subdirectory and some symbols in the generated code will be different from the example code in this guide.
wash new component hello --template-name hello-world-tinygo
Let's change into the newly-created folder hello
and take a look at the generated project. The file hello.go
will include imports, a main
function, a Hello
struct, and an HTTP handler. Let's walk through these pieces in depth.
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
The import section of this file is simple, we just import our generated types and bindings from the gen
directory and create some type aliases to make the code more readable. These type aliases will go away once we aren't working with pinned release candidate versions of WASI interfaces.
type HttpServer struct{}
func init() {
httpserver := HttpServer{}
// Set the incoming handler struct to HttpServer
http.SetExportsWasiHttp0_2_0_IncomingHandler(httpserver)
}
The init
section of this file is where we set the incoming handler struct to HttpServer
. This is the struct that will handle incoming HTTP requests.
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)
}
The business logic of our component is contained within the Handle
method. Within the Handle
method, the component receives the HTTP request, creates an OutgoingResponse
and writes that response out back to the requesting http client (such as a curl
command or a web browser).
//go:generate wit-bindgen tiny-go wit --out-dir=gen --gofmt
func main() {}
Lastly, this Go directive will ensure that when we build our project with tinygo build
, the wit-bindgen
tool will be run to generate the types and bindings for our component.
Creating the scaffold for a new component in TypeScript is easy. We will create a component that accepts an HTTP request and responds with "Hello from TypeScript!". To create your new component project, change to the directory where you want the project to be created, and enter the command below. The first term on the command (hello
) is the project name. If you choose a different project name, the name of the subdirectory and some symbols in the generated code will be different from the example code in this guide.
wash new component hello --template-name hello-world-typescript
Let's change into the newly-created folder hello
and take a look at the generated project. The file http-hello-world.ts
will include imports and a handle
function exported under incomingHandler
. Let's walk through these pieces in depth.
import { IncomingRequest, ResponseOutparam, OutgoingResponse, Fields } from 'wasi:http/types@0.2.0';
The import section of this file is simple, we just import our generated types (from jco
) for the WASI HTTP interface.
// 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 });
}
The business logic of our component is contained within the handle
function. Within the handle
function, the component receives the HTTP request, creates an OutgoingResponse
and writes that response back out to the requesting HTTP client (such as a curl
command or a web browser).
export const incomingHandler = {
handle,
};
Lastly, this export statement includes the handle
function under the incomingHandler
interface, which matches the wasi:http/incoming-handler
interface declared in the application's wit file.
Creating the scaffold for a new component in Python is easy. We will create a component that accepts an HTTP request and responds with "Hello from Python!". To create your new component project, change to the directory where you want the project to be created, and enter the command below. The first term on the command (hello
) is the project name. If you choose a different project name, the name of the subdirectory and some symbols in the generated code will be different from the example code in this guide.
wash new component hello --template-name hello-world-python
Let's change into the newly-created folder hello
and take a look at the generated project. The file app.py
will include imports and a handle
function on a IncomingHandler
class. Let's walk through these pieces in depth.
from hello import exports
from hello.types import Ok
from hello.imports.types import (
IncomingRequest, ResponseOutparam,
OutgoingResponse, Fields, OutgoingBody
)
The import section of this file is simple, we just import our generated types (from componentize-py
) for the WASI HTTP interface.
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))
The business logic of our component is contained within the handle
method. Within the handle
method, the component receives the HTTP request, creates an OutgoingResponse
and writes that response back out to the requesting HTTP client (such as a curl
command or a web browser).
This method is defined on the IncomingHandler
class, which matches the wasi:http/incoming-handler
interface declared in the application's wit file.
We're looking to add more examples in languages that support Wasm components. If you prefer working in a language that isn't listed here, let us know!
If you use an IDE that comes with code completion and hover-tooltips, you'll be able to see documentation and get strongly-typed guidance as you develop code to interact with the WASI interfaces.
The above component example works without any modification in wasmCloud, but is specifically built using standard WebAssembly tooling. You do not need to use a wasmCloud specific SDK to build components, and the above example will work directly with any WebAssembly runtime that supports the Wasm component model.
Something's missing
Before we get into modifying the scaffolding to create the rest of this component, take a look at what's not included in this code. This code returns an abstraction of an HTTP response. It is not tightly coupled to any particular HTTP server. Furthermore, you don't see the port number or server configuration options anywhere in the code. Finally, you can scale and compose this component any way you see fit without ever having to recompile or redeploy it.
This method of interface driven development is a core piece of the wasmCloud project philosophy. You should be able to write business logic in a language of your choice without having to worry about the non-functional requirements of your application, letting you configure loosely coupled abstractions at runtime. Moving on past our project scaffolding, the next step is to build and run our component.