Elysia is a web framework built specifically for Bun, leaning into Bun's speed and TypeScript-first design. It offers Express-like ergonomics with end-to-end type safety (its Eden client gives you typed API calls). Deploying it means running Bun in a container — fast startup, small footprint, minimal ceremony.
A typed Elysia API
bun add elysia// src/index.ts
import { Elysia, t } from 'elysia';
const app = new Elysia()
.get('/health', () => ({ status: 'ok' }))
.get('/api/posts/:id', ({ params }) => ({ id: params.id }), {
params: t.Object({ id: t.String() })
})
.listen({ port: Number(process.env.PORT ?? 3000), hostname: '0.0.0.0' });
console.log(`elysia on :${app.server?.port}`);The t schema validators give you runtime validation *and* static types from one definition — that's Elysia's core appeal. Binding to 0.0.0.0 is required so the platform proxy can reach the server inside the container.
Connecting PostgreSQL
Bun ships a native, fast SQL client, but for a stable API the postgres (postgres.js) library works great and is widely used:
import postgres from 'postgres';
const sql = postgres(process.env.DATABASE_URL!, { max: 10 });
app.get('/api/posts', async () => {
return await sql`SELECT * FROM posts LIMIT 20`;
});Alternatively, Bun's built-in Bun.sql / bun:sqlite and Drizzle ORM all work; pick what your team knows. Keep the pool modest (max: 10) and sized under your database's connection limit across all replicas.
Containerize with the official Bun image
Bun's official image makes this trivial. The key optimization is bun install --frozen-lockfile in its own layer for cache reuse:
FROM oven/bun:1 AS deps
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile --production
FROM oven/bun:1
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["bun", "run", "src/index.ts"]No transpile step needed — Bun runs TypeScript directly. That keeps the image lean and cold starts quick.
Optional: compile to a single binary
Bun can compile your app to a standalone executable, which trims startup further:
bun build src/index.ts --compile --outfile serverThen CMD ["./server"]. This is great for scale-to-zero where every millisecond of cold start counts.
Deploying on PandaStack
- 1Provision a managed PostgreSQL (14.x or 16.x). PandaStack injects
DATABASE_URL; postgres.js reads it directly. - 2Connect the Git repo as a container app. The Dockerfile is auto-detected; Bun is also supported as a runtime/install option if you skip the Dockerfile.
- 3Set
NODE_ENV=production. The platform providesPORT; the code already reads it. - 4Add a custom domain; SSL is automatic.
Bun's fast startup is a genuine advantage on scale-to-zero tiers — the first request after idle pays a smaller cold-start penalty than a heavier Node stack. For consistently low latency on production traffic, run on always-on compute.
Production notes
- Validation everywhere: use Elysia's
tschemas on params, query, and body — you get both safety and OpenAPI generation via the Swagger plugin. - Graceful shutdown: handle
SIGTERMto close the SQL pool before exit. - Eden client: if you have a TypeScript frontend, Elysia's Eden gives typed calls without codegen — share the app type.
process.on('SIGTERM', async () => {
await sql.end();
process.exit(0);
});Verifying
curl -s https://api.example.com/health
# {"status":"ok"}
curl -s https://api.example.com/api/posts | headHealthy responses plus real rows confirm Bun and PostgreSQL are wired correctly.
References
- [Elysia documentation](https://elysiajs.com/)
- [Bun official Docker guide](https://bun.sh/guides/ecosystem/docker)
- [Bun single-file executables](https://bun.sh/docs/bundler/executables)
- [postgres.js (postgres npm)](https://github.com/porsager/postgres)
---
Elysia on Bun is a small, fast container that scale-to-zero loves — and PandaStack supports Bun natively while injecting DATABASE_URL from a managed PostgreSQL. Start free at [dashboard.pandastack.io](https://dashboard.pandastack.io).