Web Services
On this page 7
A service written in Home is a single native binary. There is no interpreter to install in the image, no collector deciding when to pause a request, and no start-up cost beyond loading the executable. HTTP, JSON and database access are in the standard library.
A service, end to end
import std::http::{Server, Response}
struct User {
id: i64,
name: string,
email: string,
}
fn main(): async {
let server = Server.bind(":3000")
server.get("/health", |req| {
Response.text("ok")
})
server.get("/users/:id", async |req| {
let id = req.param("id").parse::<int>()?
match await database.find_user(id) {
Some(user) => Response.json(user),
None => Response.status(404).text("Not found"),
}
})
server.post("/users", async |req| {
let user: User = await req.json()?
let created = await database.create_user(user)
Response.status(201).json(created)
})
print("Server running on http://localhost:3000")
await server.listen()
}
Two things worth noticing. The 404 is a branch of a match, not a null check
someone might forget, and ? on the parse propagates the error into the
response rather than throwing past the handler.
Requests that can fail
Every fallible step in a handler returns a Result, so the error path is
written once and reads in order:
fn load_profile(id: int): Result<Profile, ApiError> {
let user = database.find_user(id)?
let prefs = database.find_preferences(user.id)?
let avatar = storage.signed_url(user.avatar_key)?
Ok(Profile { user, prefs, avatar })
}
No exception unwinds through the connection handler, and no failure is silently swallowed. See error handling.
Making outbound calls
import std::http
let response = await http.get("https://api.example.com/users")?
let users: []User = response.json()?
Timeouts, retries and POST bodies are covered in the standard library reference.
Concurrency
Handlers are async, and awaiting one request does not block the others. See
async for tasks, channels and the structure of the
scheduler.
The TypeScript side
If your service is TypeScript today, the same binary type-checks it, so you can adopt Home for one hot endpoint without moving the rest:
home tsc --noEmit
home build src/hot-path.home -o hot-path
See TypeScript migration for the staged
version of that, and the TypeScript compiler for what
home tsc covers.
Status
std::http and std::json are documented and usable through the interpreter.
The Bun-compatible runtime, which is what makes the JavaScript side of a mixed
service run on Home's own JavaScriptCore realm, is maturing: 24 node:*
modules are callable so far and the default home run still delegates to
Pantry's bun. See Bun parity and
Node parity for the module-by-module picture.