nim-servy

Production-ready async web framework for Nim

Features

JSON First

Built-in req.parseJsonBody() and res.json(data) helpers.

CORS

Configurable CORS middleware with origins, methods, headers, credentials.

Rate Limiting

Per-IP rate limiting with configurable window and limits.

Security Headers

CSP, X-Frame-Options, HSTS, X-Content-Type-Options, Referrer-Policy.

Request ID

Auto-generate or pass-through X-Request-ID for tracing.

Request Timing

X-Response-Time header on every response.

Body Size Limits

Reject oversized request bodies with 413 error.

Graceful Shutdown

SIGINT/SIGTERM handled cleanly, existing requests finish.

Keep-Alive

Connection reuse with configurable timeout.

Error Handling

Uncaught exceptions return 500 JSON, not crash.

WebSocket

Full WebSocket support built from scratch. Zero deps.

Health Check

Built-in /health endpoint returning {"status":"ok"}.

Quickstart

import servy

when isMainModule:
  var router = initRouter()

  proc handleHello(req: Request, res: Response): Future[void] {.async.} =
    res.json(%*{"message": "hello world!"})

  router.addRoute("/hello", handleHello)

  let opts = newServerOptions(port = 9000)
  var s = initServy(opts, router)
  s.run()

Routing

router.addRoute("/user/:user", handleUser)
router.addRoute("/multi/:first/:second", handleMulti)
router.addRoute("/api", handleApi, HttpPost)

Captured params: req.urlParams["user"]

Query params: req.queryParams["page"]

JSON

# Parse request body
let body = req.parseJsonBody()

# Send JSON response
res.json(%*{"status": "ok"})
res.json(%*{"error": "not found"}, Http404)

Middleware

Production middleware included out of the box:

import servy/middleware_prod

let cors = newCorsMiddleware()
let limiter = newRateLimiter(limit = 100, windowSeconds = 60)
let security = newSecurityHeadersMiddleware()
let reqId = requestIdMiddleware()
let timing = requestTimingMiddleware()
let gzip = gzipMiddleware()

var s = initServy(opts, router, @[reqId, security, cors, timing])

newCorsMiddleware

Configurable origins, methods, headers, credentials.

newRateLimitMiddleware

Per-IP rate limiting with sliding window.

newSecurityHeadersMiddleware

CSP, HSTS, X-Frame-Options, X-XSS-Protection.

requestIdMiddleware

X-Request-ID header for distributed tracing.

requestTimingMiddleware

X-Response-Time header.

gzipMiddleware

Content-Encoding for large responses.

newRequestSizeLimitMiddleware

413 error for oversized bodies.

requestLoggerMiddleware

Apache-style access logs.

Server

let opts = newServerOptions(
  address = "0.0.0.0",
  port = 8080,
  debug = true,
  maxBodySize = 10 * 1024 * 1024,
  requestTimeout = 30,
  keepAlive = true
)

WebSocket

Built-in. No external dependencies.

proc handleWS(req: Request, res: Response): Future[void] {.async.} =
  var ws = await newServyWebSocket(req)
  await ws.send("Welcome!")
  while ws.readyState == Open:
    let msg = await ws.receiveStrPacket()
    await ws.send("echo: " & msg)

router.addRoute("/ws", handleWS, HttpGet)