Documentation menu
Package

serez-http

HTTP and WebSocket server for Serez Code — Express/Flask-style API with built-in rate limiting, CORS, and middleware support.

Install

sz install serez-http

serez-http requires the Socket and Time permissions. These are declared inside the package — apps using serez-http via sz install do not need to add them to their own serez.json.

Quick start

import "serez-http"

const app = new App()

app.GET("/", fn(req, res) {
    // dicts are typed: <string, any> with ({"key", value}) entries
    let r <string, any> = ({"message", "hello from serez-http"})
    res.json(r)
})

app.GET("/user/:id", fn(req, res) {
    let id = req["params"]["id"]
    let r <string, any> = ({"user_id", id})
    res.json(r)
})

app.POST("/echo", fn(req, res) {
    let data = JSON.parse(req["body"])
    res.json(data)
})

app.listen(3000, fn() {
    out "server running on port 3000"
})

Routes

Register handlers with GET, POST, PUT, and delete. Route patterns support :param segments captured in req["params"]:

app.GET("/posts/:id/comments/:cid", fn(req, res) {
    let post_id    = req["params"]["id"]
    let comment_id = req["params"]["cid"]
    let r <string, any> = ({"post", post_id}, {"comment", comment_id})
    res.status(200).json(r)
})

Request object

FieldTypeDescription
req["method"]stringHTTP verb: GET, POST, PUT, DELETE
req["path"]stringURL path without query string
req["query"]dictParsed query parameters
req["params"]dictRoute :param captures
req["body"]stringRaw request body
req["headers"]dictAll request headers (lowercase keys)
req["content_type"]stringContent-Type header value
req["authorization"]stringAuthorization header value
req["host"]stringHost header value
req["ip"]stringClient IP (from X-Forwarded-For)

Response methods

MethodDescription
res.json(obj)Send JSON body
res.send(text)Send plain text body
res.status(code)Set HTTP status code — chainable
res.header(key, val)Add a response header — chainable
res.cookie(name, val)Set a cookie header — chainable
res.redirect(url)302 redirect
res.sendfile(path)Send file contents
res.download(path)Trigger browser download
// Chaining status + json
let nf <string, any> = ({"error", "not found"})
res.status(404).json(nf)

// Chaining header + json
let ok <string, any> = ({"ok", true})
res.header("X-Request-Id", "abc123").json(ok)

Middleware

Register middleware with addMw. Each middleware receives req, res, and next. Call next() to pass control to the next middleware or route handler. Not calling it stops the chain.

// Logging middleware
app.addMw(fn(req, res, next) {
    out req["method"] + " " + req["path"]
    next()
})

// Auth middleware
app.addMw(fn(req, res, next) {
    let token = req["authorization"]
    if (token == "") {
        let e <string, any> = ({"error", "unauthorized"})
        res.status(401).json(e)
    } else {
        next()
    }
})

Security

Built-in rate limiting and CORS helpers:

// Rate limiting — max 100 requests per minute per IP
app.addMw(fn(req, res, next) {
    if (app.rateLimit(req["ip"], 100, 60)) {
        next()
    } else {
        let e <string, any> = ({"error", "too many requests"})
        res.status(429).json(e)
    }
})

// CORS middleware factory
app.addMw(corsMiddleware(
    "https://my-domain.com",
    "GET, POST, PUT, DELETE",
    "Content-Type, Authorization"
))

Error handler

app.error(fn(err, req, res) {
    out "error: " + err
    let e <string, any> = ({"error", "internal server error"})
    res.status(500).json(e)
})

WebSocket

WebSocket support is live — the Serez Code core provides Crypto.sha1base64 and Socket.recvWsFrame / Socket.sendWsFrame. Open a WebSocket endpoint with app.ws:

app.ws("/ws", fn(req, ws) {
    ws.on("message", fn(msg) {
        ws.send("echo: " + msg)
    })
    ws.on("close", fn() {
        out "client disconnected"
    })
    ws.listen()
})

Notes

serez-http is single-threaded — one connection is handled at a time. The server listens on 127.0.0.1; use serez-apipack to deploy to Docker with external access.