Express.js-style web framework for the Hemlock programming language
Sprout is a lightweight, fast, and flexible web framework that brings the familiar Express.js API to Hemlock. Build web applications and APIs with routing, middleware, and all the features you'd expect from a modern web framework.
- Express-like API - Familiar routing and middleware patterns
- Route Parameters -
:id,:name?(optional),:version(v1|v2)(regex constraints) - Wildcard Routes -
/files/*captures the rest of the path - Middleware Stack - Global, path-specific, and route-level middleware
- Sub-Routers - Mount routers at path prefixes
- Request/Response Objects - Full-featured req/res with headers, cookies, body parsing
- Static File Serving - Serve files from directories with MIME type detection
- Built-in Middleware - JSON parsing, CORS, logging, cookies, error handling
import { App } from "hemlang/sprout";
let app = App(null);
app.get("/", fn(req, res, next) {
res.send("Hello, World!");
});
app.get("/users/:id", fn(req, res, next) {
let user_id = req.params["id"];
res.json({ user_id: user_id });
});
app.listen(3000, fn() {
print("Server running on http://localhost:3000");
});
Install via hpm:
hpm install hemlang/sproutThen import in your code:
import { App, Router, json, cors, logger } from "hemlang/sprout";
let app = App({
trust_proxy: false, // Trust X-Forwarded-* headers
case_sensitive_routing: false, // /Foo and /foo are different
strict_routing: false // /foo and /foo/ are different
});
app.get(path, handler);
app.post(path, handler);
app.put(path, handler);
app.delete(path, handler);
app.patch(path, handler);
app.head(path, handler);
app.options(path, handler);
app.all(path, handler); // All HTTP methods
app.use(middleware); // Global middleware
app.use("/api", middleware); // Path-specific middleware
app.use("/api", router); // Mount sub-router
app.static("/assets", "./public", {
max_age: 86400,
index: "index.html",
dotfiles: "ignore"
});
app.listen(3000, fn() {
print("Server started!");
});
let api = Router();
api.get("/users", fn(req, res, next) {
res.json({ users: [] });
});
api.post("/users", fn(req, res, next) {
res.status(201).json({ created: true });
});
// Mount at /api/v1
app.use("/api/v1", api);
| Property | Description |
|---|---|
req.method |
HTTP method (GET, POST, etc.) |
req.path |
Request path |
req.url |
Full URL including query string |
req.params |
Route parameters object |
req.query |
Query string parameters object |
req.body |
Parsed request body |
req.headers |
Request headers object |
req.cookies |
Parsed cookies object |
req.ip |
Client IP address |
req.protocol |
"http" or "https" |
req.hostname |
Request hostname |
req.get("content-type"); // Get header value
req.get("x-custom", "default"); // With default
req.has("authorization"); // Check if header exists
req.is("json"); // Check content type
req.accepts("json"); // Check Accept header
res.status(201); // Set status code
res.set("X-Custom", "value"); // Set header
res.header("X-Custom", "value"); // Alias for set()
res.type("json"); // Set Content-Type
res.type("application/xml"); // Full MIME type
res.send("Hello"); // Send text
res.send({ key: "value" }); // Auto-converts to JSON
res.json({ key: "value" }); // Send JSON
res.redirect("/login"); // 302 redirect
res.redirect(301, "/new-url"); // Custom status redirect
res.cookie("session", "abc123", {
max_age: 3600,
http_only: true,
secure: true,
same_site: "strict",
path: "/",
domain: ".example.com"
});
res.clear_cookie("session");
res.write("chunk 1");
res.write("chunk 2");
res.end();
All response methods return self for chaining:
res.status(201).set("X-Id", "123").json({ created: true });
// Literal path
app.get("/users", handler);
// Named parameter
app.get("/users/:id", handler); // req.params["id"]
// Multiple parameters
app.get("/posts/:year/:month/:slug", handler);
// Optional parameter
app.get("/docs/:page?", handler); // page is optional
// Regex constraint
app.get("/api/:version(v1|v2)/status", handler);
// Wildcard (captures rest of path)
app.get("/files/*", handler); // req.params["*"]
app.use(json());
app.use(urlencoded());
app.use(cookie_parser());
app.use(cors({
origin: "*",
methods: ["GET", "POST", "PUT", "DELETE"],
credentials: true
}));
app.use(logger("dev")); // Development format
app.use(logger("common")); // Common log format
app.use(error_handler(fn(err, req, res, next) {
print("Error: " + err);
res.status(500).json({ error: "Internal Server Error" });
}));
Middleware functions receive req, res, and next:
fn auth_middleware(req, res, next) {
let token = req.get("authorization", null);
if (token == null) {
res.status(401).json({ error: "Unauthorized" });
return;
}
// Continue to next middleware/route
next();
}
app.get("/protected", auth_middleware, fn(req, res, next) {
res.json({ secret: "data" });
});
Routes can have multiple handlers (middleware chain):
app.get("/admin",
auth_middleware,
admin_check_middleware,
rate_limit_middleware,
fn(req, res, next) {
res.json({ admin: true });
}
);
See example.hml for a complete example demonstrating all features.
hemlock example.hmlThen visit http://localhost:8080
- Hemlock programming language
@stdlib/netfor TCP networking@stdlib/jsonfor JSON parsing
MIT License - see LICENSE file