Asynchronous I/O & HTTP
Nostos provides fully asynchronous I/O for network and file access. Write synchronous-looking code while the runtime handles concurrency transparently.
HTTP Client
Make HTTP requests with the built-in Http module. Operations are non-blocking.
main() = {
try {
resp = Http.get("https://httpbin.org/ip")
println("Your IP: " ++ resp.body)
} catch { e ->
println("Error: " ++ e)
}
}
HTTP Server
Build web servers with stdlib.server. The serve() function spawns a lightweight process per request.
use stdlib.server.{serve, respondText, respond404}
handle(req) = match req.path {
"/" -> respondText(req, "Hello from Nostos!")
"/echo" -> respondText(req, req.body)
_ -> respond404(req)
}
main() = {
println("Server: http://localhost:8080")
serve(8080, handle)
}
Query Parameters
Use getParam() to look up values in query params, cookies, form params, or headers.
use stdlib.server.{serve, getParam, respondText, respond400}
handleSearch(req) = {
query = getParam(req.queryParams, "q")
limit = getParam(req.queryParams, "limit")
if query == "" then respond400(req, "Missing 'q' parameter")
else respondText(req, "Searching: " ++ query)
}
main() = serve(8080, handleSearch)
Form Handling
Process POST data with req.formParams. URL decoding is automatic.
use stdlib.server.{serve, getParam, respondText, respond400, respond405}
handleSubmit(req) = {
if req.method != "POST" then respond405(req)
else {
name = getParam(req.formParams, "name")
email = getParam(req.formParams, "email")
if name == "" then respond400(req, "Name required")
else respondText(req, "Thanks, " ++ name ++ "!")
}
}
Type-Safe Request Parsing
Use requestToType to parse query and form parameters directly into typed records. Returns Ok(record) on success or Err(message) with details about missing or invalid fields.
use stdlib.server.{serve, respondText, respond400}
type Result[T, E] = Ok(T) | Err(E)
type Option[T] = Some(T) | None
# Define your parameter type
type CreateUserParams = { name: String, age: Int, email: Option[String] }
handleCreateUser(req) = {
# Sugar syntax: compiler infers "CreateUserParams" from type annotation
result: Result[CreateUserParams, String] = req.toType()
match result {
Ok(params) -> {
# params.name is String, params.age is Int
# params.email is Option[String] (None if not provided)
respondText(req, "Created user: " ++ params.name ++ ", age " ++ show(params.age))
}
Err(msg) -> respond400(req, msg)
}
}
main() = serve(8080, handleCreateUser)
Supported field types:
String- Used as-is from requestInt,Int32,Int64- Parsed from stringFloat,Float64- Parsed from stringBool- Accepts "true"/"false", "1"/"0", "yes"/"no"Option[T]- Defaults toNoneif field is missing
Error messages include the field name and what went wrong:
# Missing required field:
# Err("missing required field 'age'")
# Invalid type:
# Err("field 'age': expected Int, got 'not_a_number'")
Cookies
Read cookies with getParam(req.cookies, name). Set cookies with helper functions.
use stdlib.server.{serve, getParam, respondText, respondTextWith, setCookieWithAge, clearCookie}
handleLogin(req) = {
user = getParam(req.queryParams, "user")
respondTextWith(req, "Welcome " ++ user, [setCookieWithAge("session", user, 3600)])
}
handleLogout(req) = respondTextWith(req, "Logged out", [clearCookie("session")])
handleProfile(req) = {
session = getParam(req.cookies, "session")
if session == "" then respondText(req, "Not logged in")
else respondText(req, "Session: " ++ session)
}
HTML Templating
Build type-safe HTML with Html(...). Tags are scoped functions inside the constructor.
use stdlib.html.{Html, render}
use stdlib.server.{serve, respondHtml}
userCard(name: String, email: String) = Html(
el("div", [("class", "card")], [
h3(name),
p("Email: " ++ email)
])
)
handleHome(req) = {
content = Html(div([
h1("Welcome"),
userCard("Alice", "alice@example.com"),
userCard("Bob", "bob@example.com")
]))
respondHtml(req, render(content))
}
main() = serve(8080, handleHome)
URL Routing
Use Server.matchPath to extract path parameters like :id.
use stdlib.server.{serve, respondText, respond404}
route(req) = {
path = req.path
if path == "/" then respondText(req, "Home")
else {
params = Server.matchPath(path, "/users/:id")
if length(params) == 1 then {
(_, userId) = head(params)
respondText(req, "User: " ++ userId)
} else respond404(req)
}
}
main() = serve(8080, route)
WebSockets
Handle WebSocket connections with req.isWebSocket and the WebSocket module.
use stdlib.server.{serve, respondText}
echoLoop(ws) = {
ok = try {
msg = WebSocket.recv(ws)
WebSocket.send(ws, "Echo: " ++ msg)
true
} catch { _ -> false }
if ok then echoLoop(ws) else WebSocket.close(ws)
}
route(req) = {
if req.isWebSocket then {
ws = WebSocket.accept(req.id)
echoLoop(ws)
} else respondText(req, "WebSocket server at ws://localhost:8080/")
}
main() = serve(8080, route)
PostgreSQL with Connection Pool
Use an mvar to create a simple connection pool. Get a connection for each request, return it when done.
use stdlib.server.{serve, getParam, respondText, respond400}
# Connection pool using mvar
mvar pool: List[Int] = []
getConn() = match pool {
[] -> Pg.connect("host=localhost user=postgres password=postgres")
[conn | rest] -> { pool = rest; conn }
}
releaseConn(conn) = { pool = conn :: pool }
# Handlers
listUsers(req) = {
conn = getConn()
rows = Pg.query(conn, "SELECT id, name FROM users", [])
releaseConn(conn)
respondText(req, show(rows))
}
createUser(req) = {
name = getParam(req.formParams, "name")
if name == "" then respond400(req, "name required")
else {
conn = getConn()
Pg.execute(conn, "INSERT INTO users (name) VALUES ($1)", [name])
releaseConn(conn)
respondText(req, "Created: " ++ name)
}
}
route(req) = match req.path {
"/users" -> if req.method == "POST" then createUser(req) else listUsers(req)
_ -> respondText(req, "GET/POST /users")
}
main() = serve(8080, route)
stdlib.server API:
serve(port, handler)- Start server with spawn-per-requestgetParam(params, key)- Look up key in param listreq.toType()- Parse request params to typed record (with type annotation)respondText(req, body)- Send text responserespondHtml(req, body)- Send HTML responserespondJson(req, body)- Send JSON responserespond400(req, msg)- Bad Requestrespond404(req)- Not Foundredirect(req, url)- 302 redirectsetCookieWithAge(name, value, seconds)- Cookie header tuplewsLoop(ws, handler)- WebSocket message loop