I’ve been working on sarcophagus, a higher-level API layer for Nim’s Mummy web server.
The current focus is tapis, a typed API/router layer inspired by FastAPI but designed around Nim’s static typing and metaprogramming. It lets you define routes as regular Nim procs and automatically handles parameter decoding, response serialization, OpenAPI generation, and structured error responses.
Example:
proc readItem(id: int, verbose: Option[bool]): ItemOut {.
gcsafe, tapi(get, "/items/@id", summary = "Read item")
.} =
ItemOut(id: id, verbose: verbose.get(false))
api.add(readItem)
It also supports grouped params:
proc listItems(params: Params[ListItemsParams]): ItemList {.
gcsafe, tapi(get, "/items")
.} =
...
And OAuth2 security:
let readSecurity = oauth2(config, ["items:read"])
withSecurity(api, readSecurity):
api.add(listItems)
api.add(readItem)
api.registerOAuth2(config)
api.mountOpenApi()
Features included so far:
There’s a secure example app in examples/tapis_secure that implements a small “goto” service with scoped OAuth2 and OpenAPI output.
Repo: https://github.com/elcritch/sarcophagus
I’d be interested in feedback on the API shape, especially the balance between FastAPI-style flat handlers and Nim-style typed wrapper objects.
I made some more improvements to documentations, moved to upstream jwt, and added (optional) msgpack4nim support.
Here's a small complete example:
import std/options
import mummy
import sarcophagus/tapis
type Item = object
id*: int
name*: string
verbose*: bool
proc readItem(id: int, verbose: Option[bool]): Item {.
tapi(get, "/items/@id", summary = "Read an item", tags = ["items"])
.} =
Item(id: id, name: "item-" & $id, verbose: verbose.get(false))
proc createItem(item: Item): ApiResponse[Item] {.
tapi(post, "/items", summary = "Create an item", responseStatus = 201)
.} =
apiResponse(item, statusCode = 201)
let api = initApiRouter("Example API", "1.0.0")
api.add(readItem)
api.add(createItem)
api.mountOpenApi()
echo "Listening on http://127.0.0.1:8080"
newServer(api.router).serve(Port(8080), address = "127.0.0.1") There's also a couple more security modules. sarcophagus/security/secret_hashing wraps BearSSL to provide a "pbkdf2-sha256" secure digest with a hashSecret and verifySecret.
I also added sarcophagus/security/oauth2_hashed_clients that adds reusable OAuth2 client credential plumbing for applications that store hashed client secrets instead of plaintext secrets. Storage is callback-based, so an application can back it with SQLite, Postgres, flat files, or another store.
Because who has time to recreate the basic oauth2 and secret hashing, salting, and verification stuff every time.
proc swaggerUiHandler(request: Request) {.gcsafe.} =
var headers = emptyHttpHeaders()
headers["Content-Type"] = "text/html"
request.respond(200, headers, """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>StockHub API Docs</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="redoc"></div>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
<script>
Redoc.init(window.location.origin + '/swagger.json', {}, document.getElementById('redoc'));
</script>
</body>
</html>""")
api.router.addRoute("GET", "/docs", swaggerUiHandler) I tried to host api doc, seems no easy way add example response to swagger.jsonI tried to host api doc, seems no easy way add example response to swagger.json
As in adding a pre-made example of the data input/output and is that something normally auto-generated or that developers add?
Something like that can be added. The trick will be doing it at compile time and the corresponding api to do it. Perhaps using a compileTime proc and the tapi macro or the api router methods.
What are use-cases explained to non-computer-scientists?
Scientists often use abstract names for concepts they use, which use other abstract names, which use other abstract names, etc., thus leading to what i call abstraction-swamp. It can take some time for an illiterate to find a known base of of known concepts, and only after that they can reconstruct the material to learn the presented stuff.
What are use-cases explained to non-computer-scientists?
The name Sarcophagus is just a word play on Mummy and dealing somewhat with security stuff.
Very simply Sarcophagus is a FastAPI-like layer for Mummy.
It provides the ability to write JSON APIs for Mummy using Nim types which are parsed automatically for you. It also generates swagger.json docs automatically.
There's also support securing endpoints using oauth2 token based security which has become pretty standard for JSON/REST APIs. The oauth2 stuff can be used directly with pure Mummy as well.
Just release Sarcophagus v0.11.
It includes CORS support, XSS protections, and signed cookies. It also adds more support for user logging.
I also had Codex GPT-5.5 create a Security Guide because I forget the half dozen or so things you're supposed do setup for secure websites like CORS, XSS, etc.
Finally, got a cute header logo. These LLMs are just insane nowadays:

Nim[rod], Mummy, Sarcophagus.
I like how this working itself into being ancient near-East theming.
Sorry for spamming! Just discovered this while working on a GUI app for Nimble (will share this soon!) via forum's XML feed, lol.
@clonk, if you don't mind loading a C library (Libevent), you can use Supranim's low-level API (the high-level framework is not ready) to create a streaming service. Supranim provides zero-copy file streaming via streamFile
Here, testing with Big Buck Bunny 4K (see Wikipedia) ~2.76 GB in size.
{.passL:"-L/opt/local/lib -levent -levent_pthreads", passC:"-I /opt/local/include".}
import std/[tables, uri, httpcore, os, options]
from std/net import Port
import supranim/network/webserver
proc onRequest(req: var Request) =
case req.path
of "/":
req.send(200, "Hello, World!")
of "/stream":
let path = "./Big_Buck_Bunny_4K.webm"
let headers = newHttpHeaders()
headers.add("Content-Type", "video/webm")
req.streamFile(path, headers)
else:
req.send(404, "Not Found")
var server = newWebServer(8000.Port)
server.start(onRequest) API Ref: https://supranim.github.io/supranim/supranim/network/webserver.html
Sure, you can combine pkg/mimedb to get the content type based on the file extension. Cheers!