Custom APIs
On startup TrailBase will automatically load any WASM component, i.e. *.wasm
files, that it finds in <traildepot>/wasm.
This can be used to implement arbitrary HTTP APIs with custom handlers.
Examples
Section titled “Examples”At this point the documentation is embarrassingly “thin”, take a look at the examples
for further context. Some are provided here, more can be found in /examples including
project templates:
- TypeScript:
/examples/wasm-guest-ts - JavaScript:
/examples/wasm-guest-js - Rust:
/examples/wasm-guest-rust
(Java|Type)Script
Section titled “(Java|Type)Script”The following example demonstrates how to:
- register a parameterized route with
{table}, - query the database,
- return a string body or HTTP error.
import { defineConfig } from "trailbase-wasm";import { query } from "trailbase-wasm/db";import { HttpHandler, HttpResponse, StatusCode } from "trailbase-wasm/http";import type { HttpRequest } from "trailbase-wasm/http";
async function countRecordsHandler(req: HttpRequest): Promise<HttpResponse> { const table = req.getPathParam("table"); if (!table) { return HttpResponse.status( StatusCode.BAD_REQUEST, `Table not found for '?table=${table}'`, ); }
const rows = await query(`SELECT COUNT(*) FROM ${table}`, []); return HttpResponse.text(`count: ${rows[0][0]}`);}
export default defineConfig({ httpHandlers: [HttpHandler.get("/count/{table}", countRecordsHandler)],});Another example reading query parameters and producing a JSON response.
import { defineConfig } from "trailbase-wasm";import { HttpHandler, HttpRequest, HttpResponse } from "trailbase-wasm/http";import { query } from "trailbase-wasm/db";
async function searchHandler(req: HttpRequest): Promise<HttpResponse> { // Get the query params from the url, e.g. '/search?aroma=4&acidity=7'. const aroma = req.getQueryParam("aroma") ?? 8; const flavor = req.getQueryParam("flavor") ?? 8; const acid = req.getQueryParam("acidity") ?? 8; const sweet = req.getQueryParam("sweetness") ?? 8;
// Query the database for the closest match. const rows = await query( `SELECT Owner, Aroma, Flavor, Acidity, Sweetness FROM coffee ORDER BY vec_distance_L2( embedding, FORMAT("[%f, %f, %f, %f]", $1, $2, $3, $4)) LIMIT 100`, [+aroma, +flavor, +acid, +sweet], );
return HttpResponse.json(rows);}
export default defineConfig({ httpHandlers: [HttpHandler.get("/search", searchHandler)],});The following example demonstrates how to:
- register an HTTP route,
- read query parameters,
- query the database,
- and return a JSON response or errors.
#![forbid(unsafe_code, clippy::unwrap_used)]#![allow(clippy::needless_return)]#![warn(clippy::await_holding_lock, clippy::inefficient_to_string)]
use trailbase_wasm::db::{Value, query};use trailbase_wasm::http::{HttpError, HttpRoute, Json, Request, StatusCode, routing};use trailbase_wasm::{Guest, export};
type SearchResponse = (String, f64, f64, f64, f64);
async fn search_handler(req: Request) -> Result<Json<Vec<SearchResponse>>, HttpError> { let (mut aroma, mut flavor, mut acidity, mut sweetness) = (8, 8, 8, 8);
for (param, value) in req.url().query_pairs() { match param.as_ref() { "aroma" => aroma = value.parse().unwrap_or(aroma), "flavor" => flavor = value.parse().unwrap_or(flavor), "acidity" => acidity = value.parse().unwrap_or(acidity), "sweetness" => sweetness = value.parse().unwrap_or(sweetness), _ => {} } }
// Query the closest match using vector-search. let results: Vec<SearchResponse> = query( r#" SELECT Owner, Aroma, Flavor, Acidity, Sweetness FROM coffee ORDER BY vec_distance_L2( embedding, FORMAT("[%f, %f, %f, %f]", $1, $2, $3, $4)) LIMIT 100 "#, [ Value::Integer(aroma), Value::Integer(flavor), Value::Integer(acidity), Value::Integer(sweetness), ], ) .await .map_err(|err| HttpError::message(StatusCode::INTERNAL_SERVER_ERROR, err))? .into_iter() .map(|row| { // Convert to json response. let Value::Text(owner) = row[0].clone() else { panic!("invariant"); };
return ( owner, as_real(&row[1]).expect("invariant"), as_real(&row[2]).expect("invariant"), as_real(&row[3]).expect("invariant"), as_real(&row[4]).expect("invariant"), ); }) .collect();
return Ok(Json(results));}
fn as_real(v: &Value) -> Result<f64, String> { return match v { Value::Real(f) => Ok(*f), _ => Err(format!("Not a real: {v:?}")), };}
// Lastly, implement and export a TrailBase component.struct GuestImpl;
impl Guest for GuestImpl { fn http_handlers() -> Vec<HttpRoute> { return vec![routing::get("/search", search_handler)]; }}
export!(GuestImpl);