@tsonic/express
Express-style HTTP server APIs for Tsonic.
This package is part of Tsonic: https://tsonic.org.
Use this package to write Express-like apps in TypeScript and compile them to native binaries with tsonic.
The project surface should be @tsonic/js. @tsonic/express remains a normal imported package, but its public API is JS-facing:
Date,Uint8Array,numberRecord<string, ...>and arrays- no CLR collection/runtime types in the TS-facing contract
Prerequisites
- Install the .NET 10 SDK (required by Tsonic): https://dotnet.microsoft.com/download
- Verify:
dotnet --version
Quick Start (new project)
mkdir my-api && cd my-api npx --yes tsonic@latest init --surface @tsonic/js # Install Express runtime + bindings (installs required ASP.NET Core deps too) npx --yes tsonic@latest add npm @tsonic/express # Replace the default App.ts with a minimal API cat > packages/my-api/src/App.ts <<'EOF' import { express } from "@tsonic/express/index.js"; export function main(): void { const app = express.create(); app.get("/", async (_req, res, _next) => { res.json({ ok: true }); }); app.listen(3000); } EOF npm run dev
Then open http://localhost:3000/.
Hello World
// packages/my-api/src/App.ts import { express } from "@tsonic/express/index.js"; export function main(): void { const app = express.create(); app.get("/", async (_req, res, _next) => { res.send("hello"); }); app.listen(3000); }
Basic API Surface
Handler model (important)
This package is Task-first (like ASP.NET): route handlers and middleware should be written as async functions (even if you don't await anything).
This avoids “async-void” behavior and keeps execution/exception semantics deterministic.
Also, handlers use the 3-argument signature: (req, res, next) (even for routes). If you don't need next, name it _next.
Create an app / router
import { express } from "@tsonic/express/index.js"; const app = express.create(); const router = express.Router(); router.get("/ping", async (_req, res, _next) => { res.send("pong"); }); app.use("/api", router);
Routing
Common verbs:
app.get("/health", async (_req, res, _next) => { res.send("ok"); }); app.post("/items", async (req, res, _next) => { res.json(req.body); }); app.put("/items/:id", async (req, res, _next) => { res.send(req.params["id"] ?? ""); }); app.delete("/items/:id", async (_req, res, _next) => { res.sendStatus(204); }); app.patch("/items/:id", async (_req, res, _next) => { res.sendStatus(204); }); app.all("/anything", async (_req, res, _next) => { res.send("matched"); });
Middleware
app.use(async (req, _res, next) => { // Do something with req await next(); });
CORS
Cookies
app.get("/set-cookie", async (_req, res, _next) => { res.cookie("sid", "abc"); res.send("ok"); }); app.get("/read-cookie", async (req, res, _next) => { res.json({ sid: req.cookies["sid"] }); });
Error middleware:
app.useError(async (err, _req, res, _next) => { res.status(500).json({ error: `${err}` }); });
Request / Response
Request highlights:
req.method,req.path,req.originalUrlreq.query,req.params,req.cookies,req.signedCookiesreq.body(when using body parsers)req.get(name)/req.header(name)
Response highlights:
res.status(code)res.send(body),res.json(body),res.sendStatus(code)res.redirect(path)/res.redirect(status, path)res.set(name, value)/res.header(name, value)res.cookie(name, value, options)/res.clearCookie(name, options)
Body parsing
app.use(express.json()); app.use(express.urlencoded()); app.use(express.text()); app.use(express.raw());
Multipart / file uploads
const upload = express.multipart(); app.post("/upload", upload.single("avatar"), async (req, res, _next) => { res.json({ filename: req.file?.originalname, fields: req.body, }); });
Static files
app.use(express.static("./public"));
Listen / close
const server = app.listen(3000); server.close();
Advanced docs
- docs/advanced.md (routers, handlers, middleware patterns)
- docs/deviations.md (known compatibility gaps / parity notes)
- docs/generation.md (how this package is generated)
Versioning Model
This repo is versioned by runtime major:
10->versions/10/-> npm@tsonic/express@10.x
License
MIT