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"]:

Run
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
Run
// 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.

Run
// 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:

Run
// 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

Run
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:

Run
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.