Skip to content

Greet a visitor

Serve a greeting over HTTP: a default hello at the root, and a personalised one at /hello/:name. Names must be sensible, and invalid ones should be rejected at the boundary rather than deep inside a handler.

src/hello/text.bynk
commons hello.text
---
Who we are greeting — non-empty, at most 40 characters.
A refined type: every `Subject` that exists has already passed this
check, so `greeting` never needs to validate its input.
---
type Subject = String where NonEmpty and MaxLength(40)
---
The canonical greeting for a subject.
---
fn greeting(subject: Subject) -> String {
"Hello, \(subject)!"
}
src/hello/web.bynk
context hello.web
uses hello.text
consumes bynk { Logger }
service api from http {
on GET("/") by v: Visitor () -> Effect[HttpResult[String]] given Logger {
let _ <- Logger.info("greeting the world")
Ok(greeting("World"))
}
on GET("/hello/:name") by v: Visitor (name: String) -> Effect[HttpResult[String]] given Logger {
match Subject.of(name) {
Ok(subject) => {
let _ <- Logger.info("greeting \(subject)")
Ok(greeting(subject))
}
Err(_) => BadRequest("a name must be non-empty and at most 40 characters")
}
}
}
tests/hello/text.bynk
test hello.text {
test "greets the world" {
assert greeting("World") == "Hello, World!"
}
test "greets any valid subject" {
assert greeting(Mock[Subject]("Bynk")) == "Hello, Bynk!"
}
test "rejects an empty subject" {
assert Subject.of("") is Err(_)
}
}
Open the full project ↗

This example reaches Workers-only shapes (storage bindings, agents, or cron), so it runs with bynk dev rather than in the browser playground. See Install to get started.

The language pieces live in commons hello.text. Subject is a refined String — non-empty and at most 40 characters — and greeting takes a Subject, so it never validates its argument: an invalid subject cannot be constructed, so it cannot reach the function.

The service lives in context hello.web, which uses hello.text and declares consumes bynk { Logger }. The context is the unit of deployment: it becomes one Cloudflare Worker. Its service api from http block holds two handlers. Each is declared by Visitor and given Logger, so the logging dependency is visible in the signature, supplied by the platform, and mockable in tests rather than reached for ambiently.

The handlers show where validation belongs. GET("/") greets "World" directly. GET("/hello/:name") receives a raw String and runs Subject.of(name), matching the Result: an Ok greets the validated subject, an Err returns BadRequest. The handlers return HttpResult, and the compiler generates the router, the boundary handling, and the Worker entry point around them. A test in tests/hello/text.bynk fabricates a pinned Mock[Subject] and asserts the greeting and the empty-subject rejection, with no harness code.