bini-server
v1.0.1
Published
Zero-dependency production server for bini-router apps — static files, API routes, SPA fallback
Maintainers
Readme
bini-server
Zero-dependency production server for bini-router apps.
Serves your dist/ statically and proxies /api/* to your src/app/api/ handlers — just like Vite's preview server, but for production.
Features
- ⚡ Zero dependencies — pure Node.js built-ins only
- 🗂️ Static file serving — streams
dist/with correct MIME types, ETag, and cache headers - 🌐 API routes — proxies
/api/*to yoursrc/app/api/handlers (Hono apps + plain functions) - 🔀 SPA fallback — unknown routes serve
dist/index.html - 🌿 Auto env loading —
.envloaded automatically via bini-env — no dotenv setup needed - 🏷️ ETag support —
304 Not Modifiedresponses for unchanged files - 🛡️ CORS — enabled by default on all API responses
- ⏱️ Timeouts — 30s body read + 30s handler timeout with proper error responses
- 🔒 Body limit — 10MB request body limit
- 🔌 Port auto-increment — starts at 3000, increments if busy
- 🪄 Graceful shutdown — handles SIGTERM + SIGINT with 10s force-exit fallback
- 🖥️ Cross-platform — works on Windows, macOS, and Linux
Requirements
- Node.js ≥ 18
- A bini-router project with a built
dist/
Install
npm install bini-serverUsage
Add to your package.json:
{
"scripts": {
"build": "vite build",
"start": "bini-server"
}
}Then:
npm run build # build your app
npm start # serve it in productionTerminal output:
ß Bini.js (production)
➜ Environments: .env
➜ Local: http://localhost:3000/
➜ Network: http://192.168.1.5:3000/Environment Variables
bini-server uses bini-env to automatically load .env before the server starts — no manual dotenv setup needed.
# .env
PORT=3000
# Server-side vars — used by API routes via getEnv() / requireEnv()
[email protected]
SMTP_PASS=your_password
FROM_EMAIL=App <[email protected]>
# Client-side vars — already baked into dist/ at build time, ignored by server
BINI_FIREBASE_API_KEY=your_keyClient-side
BINI_*vars are baked intodist/at build time by Vite — bini-server loads them intoprocess.envbut never uses them. They don't cause any errors.
How it works
bini-server runs a plain Node.js http server with a three-layer middleware stack:
Request
│
├─ /api/* → src/app/api/ handlers (Hono apps or plain functions)
│
├─ /* → stream static file from dist/ (with ETag + cache headers)
│
└─ /* → dist/index.html (SPA fallback)No Vite, no Express, no Fastify — just Node's built-in http module.
Port
Default port is 3000. Override via environment variable:
PORT=8080 bini-serverOr in .env:
PORT=8080If the port is busy, bini-server automatically increments and warns:
⚠ Port 3000 is in use, using port 3001 instead.Configurable Directories
By default bini-server reads from src/app/api/ and dist/. Override via env vars if your project uses a different structure:
BINI_API_DIR=src/api # default: src/app/api
BINI_DIST_DIR=build # default: distAPI Routes
bini-server reads src/app/api/ directly — the same files your dev server uses. Both Hono apps and plain function handlers are supported. getEnv and requireEnv are auto-imported by bini-router so no imports needed:
// src/app/api/email.ts
import { Hono } from 'hono'
import nodemailer from 'nodemailer'
const app = new Hono().basePath('/api')
const transporter = nodemailer.createTransport({
host: 'smtp-relay.brevo.com',
port: 587,
auth: {
user: requireEnv('SMTP_USER'), // auto-imported, throws if missing
pass: requireEnv('SMTP_PASS'),
},
})
app.post('/email', async (c) => {
const { to, subject, html } = await c.req.json()
await transporter.sendMail({ from: requireEnv('FROM_EMAIL'), to, subject, html })
return c.json({ ok: true })
})
export default appDeployment
VPS (Ubuntu, Debian, etc.)
npm run build
npm startUse pm2 to keep it running:
npm install -g pm2
pm2 start "npm start" --name my-app
pm2 save
pm2 startupRailway
Set start command to npm start in your Railway project settings. Railway injects PORT automatically.
Render
Set start command to npm start. Render injects PORT automatically.
Fly.io
# fly.toml
[processes]
app = "npm start"Project structure
bini-server expects the standard bini-router project layout:
my-app/
├── src/
│ └── app/
│ └── api/ ← API route handlers
├── dist/ ← built frontend (vite build output)
├── .env ← environment variables
└── package.jsonvs vite preview
| Feature | vite preview | bini-server |
|---|---|---|
| Serves dist/ | ✅ | ✅ |
| API routes | ✅ via bini-router | ✅ via bini-router |
| SPA fallback | ✅ | ✅ |
| Auto env loading | ✅ via bini-env | ✅ via bini-env |
| ETag / 304 support | ❌ | ✅ |
| Production use | ❌ not recommended | ✅ |
| Body timeout | ❌ | ✅ 30s |
| Body size limit | ❌ | ✅ 10MB |
| Handler timeout | ❌ | ✅ 30s |
| Graceful shutdown | ❌ | ✅ |
| Configurable dirs | ❌ | ✅ |
| Zero dependencies | ✅ | ✅ |
License
MIT © Binidu Ranasinghe
