Table of Contents

Building web APIs with nyx-serve

What is nyx-serve?

nyx-serve is an HTTP framework for Nyx that gives you routing, middleware, JSON handling, and sessions out of the box. It follows the same architecture as the nyx-kv case study: a thread pool of workers processing requests from a shared channel.

The App, Request, and Response

Every nyx-serve application starts with three structs:

struct App {
    routes: Array,
    middlewares: Array,
    static_dir: String,
}

struct Request {
    method: String,
    path: String,
    query: Map,
    headers_flat: Array,
    body: String,
    form: Map,
    cookies: Map,
    params: Map,
}

struct Response {
    status: int,
    headers_flat: Array,
    body: String,
}

Create an app, register routes, and start serving:

import "std/web"

fn handle_index(req: Request) -> Response {
    return response_html(200, "<h1>Hello!</h1>")
}

fn main() {
    let app: App = app_new()
    app_get(app, "/", handle_index)
    serve_app(app, 3000, 64)
}

Routing

Register handlers for each HTTP method:

app_get(app, "/users", list_users)
app_post(app, "/users", create_user)
app_put(app, "/users/{id}", update_user)
app_delete(app, "/users/{id}", delete_user)

Route patterns support named parameters ({id}) and wildcards (/static/*). Named parameters are accessible via req.params:

fn get_user(req: Request) -> Response {
    let id: String = req.params.get("id")
    return response_text(200, "User: " + id)
}

Middleware

Middleware functions run before your route handlers. Register them with app_use:

let app: App = app_new()
app_use(app, mw_logging)    // log every request
app_use(app, mw_cors)       // handle CORS preflight

nyx-serve ships with two built-in middleware functions:

Configure CORS before registering the middleware:

cors_configure("*", "GET,POST,PUT,DELETE", "Content-Type,Authorization")

A middleware returns a Response with status: 0 to continue the chain, or a non-zero status to short-circuit (e.g. return 403 for unauthorized requests).

JSON APIs

Parse a JSON request body into a Map:

fn create_user(req: Request) -> Response {
    let data: Map = req_json(req)
    let name: String = data.get("name")
    // ... create user ...
    return response_json(201, "{\"ok\":true}")
}

Build a JSON response from a Map:

fn get_status(req: Request) -> Response {
    let data: Map = Map.new()
    data.insert("status", "ok")
    data.insert("version", "1.0")
    return response_json_map(200, data)
}

Sessions

Sessions are cookie-backed and stored in nyx-kv via RESP. Configure the connection first:

session_configure("127.0.0.1", 6380, 3600)  // host, port, TTL in seconds

Use sessions in your handlers:

fn login(req: Request) -> Response {
    var resp: Response = response_text(200, "Logged in")
    let sid: String = session_start(req, resp)
    session_set(req, "user", "alice")
    return resp
}

fn profile(req: Request) -> Response {
    let user: String = session_get(req, "user")
    if user.length() == 0 {
        return response_text(401, "Not logged in")
    }
    return response_text(200, "Hello, " + user)
}

Static files

Serve files from a directory with serve_static:

serve_static(app, "public")

Requests to /style.css will serve public/style.css. Wildcard routes like /learn/* can also be used for custom static serving logic.

Complete example: a todo API

import "std/web"

var todos: Array = []

fn list_todos(req: Request) -> Response {
    let result: Map = Map.new()
    result.insert("count", int_to_string(todos.length()))
    return response_json_map(200, result)
}

fn add_todo(req: Request) -> Response {
    let data: Map = req_json(req)
    let title: String = data.get("title")
    todos.push(title)
    return response_json(201, "{\"ok\":true}")
}

fn main() {
    cors_configure("*", "GET,POST", "Content-Type")

    let app: App = app_new()
    app_use(app, mw_logging)
    app_use(app, mw_cors)
    app_get(app, "/todos", list_todos)
    app_post(app, "/todos", add_todo)
    serve_app(app, 3000, 64)
}

Summary

← Previous: The Package Manager Next: Real-time with Pub/Sub →