spaceship_helm

A Hono-like HTTP router for Gleam, targeting JS fetch-based runtimes.

Installation

gleam add spaceship_helm

Quick Start

import spaceship_helm
import spaceship_helm/context
import spaceship_helm/response
pub fn main() {
let app =
spaceship_helm.new()
|> spaceship_helm.get("/", fn(_ctx) {
response.text("Hello, World!")
})
// Export for JS runtimes
app |> spaceship_helm.to_fetch()
}

Features

FeatureDescription
HTTP Methodsget, post, put, delete, patch, head, options, on
Path Params:name syntax for dynamic segments
Wildcards*name to match remaining path
Query ParamsExtract query string values
MiddlewareFunctional pipe composition
Route GroupsNamespace routes with prefixes
Response Helperstext, json, html, redirect
Built-in MiddlewareCORS, logger
Static FilesServe public assets with MIME detection
SessionsCookie-based session management
CookiesRead/write HTTP cookies
Environment VariablesCross-platform env access (Node, Cloudflare, Deno, Bun)

API

Creating an App

let app = spaceship_helm.new()

Registering Routes

let app =
spaceship_helm.new()
|> spaceship_helm.get("/", home_handler)
|> spaceship_helm.post("/users", create_user)
|> spaceship_helm.put("/users/:id", update_user)
|> spaceship_helm.delete("/users/:id", delete_user)
|> spaceship_helm.patch("/users/:id", patch_user)
|> spaceship_helm.on("CUSTOM", "/custom", custom_handler)

Path Parameters

let app =
spaceship_helm.new()
|> spaceship_helm.get("/users/:id", fn(ctx) {
let id = context.param(ctx, "id")
response.text(id)
})

Query Parameters

let app =
spaceship_helm.new()
|> spaceship_helm.get("/search", fn(ctx) {
let query = context.query(ctx, "q") |> option.unwrap("")
response.text(query)
})

Route Groups

let app =
spaceship_helm.new()
|> spaceship_helm.group("/api/v1", fn(app) {
app
|> spaceship_helm.get("/users", list_users)
|> spaceship_helm.get("/users/:id", get_user)
|> spaceship_helm.post("/users", create_user)
})

Middleware

let app =
spaceship_helm.new()
|> spaceship_helm.middleware(fn(ctx, next) {
let resp = next(ctx)
response.set_header(resp, "x-powered-by", "spaceship_helm")
})
|> spaceship_helm.get("/", home_handler)

Built-in Middleware

import spaceship_helm/middleware
let app =
spaceship_helm.new()
|> spaceship_helm.middleware(middleware.cors())
|> spaceship_helm.middleware(middleware.logger(io.println))

Static Files

Serve static files from a directory:

import spaceship_helm/static
let app =
spaceship_helm.new()
|> spaceship_helm.get("/api/data", api_handler)
|> spaceship_helm.middleware(static.directory("public"))

With custom cache duration:

|> spaceship_helm.middleware(static.directory_with_cache("public", 604800))

Supported MIME types: HTML, CSS, JavaScript, JSON, images, fonts, and more.

Sessions

Cookie-based session management:

import spaceship_helm/session
// Create a session store
let store = sessions.new_store()
// Setup middleware
let app =
spaceship_helm.new()
|> spaceship_helm.get("/", home_handler)
|> spaceship_helm.use(sessions.cookie("session_id", "secret-key", store))
// In handler - read session
use session <- sessions.get(ctx)
let username = sessions.get_value(session, "username")
// In handler - write session
let session = sessions.set_value(session, "username", "alice")
sessions.commit(response, session, store)

Cookies

Read and write HTTP cookies:

import spaceship_helm/cookie
// Read cookie from request
let session_id = cookie.get(ctx.req, "session_id")
// Set cookie on response
let resp = cookie.set(response, "session_id", "abc123", 3600)
// Delete cookie
let resp = cookie.delete(response, "session_id")
// Set with custom options
let options = cookie.CookieOptions(
path: "/api",
max_age: 3600,
http_only: True,
secure: True,
same_site: "Strict",
)
let resp = cookie.set_with_options(response, "token", "xyz", options)

Response Helpers

// Text
response.text("Hello")
// HTML
response.html("<h1>Hello</h1>")
// JSON
import gleam/json
response.json(json.object([#("name", json.string("Alice"))]))
// Redirect
response.redirect("/login")
response.redirect_permanent("/new-url")
// Status codes
response.bad_request("Invalid input")
response.unauthorized()
response.forbidden()
response.not_found()
response.internal_server_error()
response.no_content()

Custom Not Found Handler

let app =
spaceship_helm.new()
|> spaceship_helm.get("/", home_handler)
|> spaceship_helm.not_found(fn(_ctx) {
response.new(404) |> response.set_body(<<"Custom 404":utf8>>)
})

JavaScript Usage

// app.gleam
import spaceship_helm
pub fn main() {
let app =
spaceship_helm.new()
|> spaceship_helm.get("/", fn(_ctx) {
spaceship_helm/response.text("Hello from Gleam!")
})
app |> spaceship_helm.to_fetch()
}
// app.mjs
import handler from "./build/dev/javascript/app.mjs"
// Bun
export default { fetch: handler }
// Cloudflare Workers
export default { fetch: handler }
// Deno
Deno.serve(handler)
// Node.js (18+)
import { createServer } from "node:http"
const server = createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`)
const request = new Request(url, { method: req.method, headers: req.headers })
const response = await handler(request)
res.writeHead(response.status, Object.fromEntries(response.headers))
res.end(await response.arrayBuffer())
})
server.listen(3000)

Environment Variables

Access environment variables across different runtimes:

import spaceship_helm/env
// Get a single variable
let value = env.get("MY_VAR") // Returns Option(String)
// Get with default
let value = env.get_or("MY_VAR", "default")
// Get required (panics if not set)
let value = env.get_required("DATABASE_URL")
// Check if exists
let exists = env.has("MY_VAR")
// Get all variables
let vars = env.all() // List(#(String, String))

The env module works on:

For Cloudflare Workers, initialize the env in your entry point:

import spaceship_helm/env as helm_env
pub fn main(req, cf_env, ctx) {
// Initialize env access with Cloudflare's env object
helm_env.init(cf_env)
// Now you can read variables
let db_url = helm_env.get("DATABASE_URL")
// Handle request
}

License

Apache-2.0