create-bini-app
v9.3.2
Published
Official CLI for Bini.js — scaffolds a React + Vite app with file-based routing, API routes, TypeScript or JavaScript, Tailwind CSS or CSS Modules, automatic favicon generation, and PWA support.
Maintainers
Keywords
Readme
██████╗ ██╗███╗ ██╗██╗ ██╗███████╗
██╔══██╗██║████╗ ██║██║ ██║██╔════╝
██████╔╝██║██╔██╗ ██║██║ ██║███████╗
██╔══██╗██║██║╚██╗██║██║ ██╗ ██║╚════██║
██████╔╝██║██║ ╚████║██║ ╚█████╔╝███████║
╚═════╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚════╝ ╚══════╝A React + Vite framework with file-based routing, API routes, and a zero-dependency production server
Quick Start
npx create-bini-app@latest my-app
cd my-app
npm install
npm run devOpens http://localhost:3000 automatically.
What You Get
Running create-bini-app scaffolds a complete project with:
- File-based routing via
bini-router—page.tsxfiles map to URLs, nested layouts, per-route metadata, automatic code splitting - API routes powered by Hono — plain function handlers or full Hono apps in
src/app/api/ - Zero-dependency production server via
bini-server— pure Node.jshttp, servesdist/and proxies/api/* - Dev overlay via
bini-overlay— animated logo badge and full error overlay with source maps - Clean startup banner via
bini-env— shows active.envfiles on dev server start - Automatic favicons — SVG, PNG at 512×512,
apple-touch-icon,og-image.png, andsite.webmanifestgenerated at scaffold time - Official plugin system — extend Bini.js with first-party and community plugins via
bini.config - TypeScript or JavaScript — your choice at scaffold time
- Tailwind CSS, CSS Modules, or plain CSS — your choice at scaffold time
- Netlify Edge Functions — API routes deploy to Netlify Edge by default, zero extra config
CLI Usage
# Interactive — prompts for name and options
npx create-bini-app@latest
# Pass project name directly
npx create-bini-app@latest my-app
# Skip prompts with flags
npx create-bini-app@latest my-app --typescript --tailwind
npx create-bini-app@latest my-app --javascript --css-modules
npx create-bini-app@latest my-app --force # overwrite existing directory| Flag | Description |
|---|---|
| --typescript | Use TypeScript |
| --javascript | Use JavaScript |
| --tailwind | Use Tailwind CSS |
| --css-modules | Use CSS Modules |
| --force | Overwrite an existing directory |
| --minimal | Scaffold with fewer files |
| --version, -v | Print CLI version |
| --help, -h | Show help |
Project Structure
my-app/
├── src/
│ ├── app/
│ │ ├── api/ ← API route handlers
│ │ │ └── hello.ts → /api/hello
│ │ ├── layout.tsx ← Root layout + global metadata
│ │ ├── page.tsx ← / (home page)
│ │ ├── not-found.tsx ← Custom 404 page (optional)
│ │ └── globals.css
│ ├── main.tsx ← React entry point
│ └── App.tsx ← Auto-generated by bini-router — do not edit
├── public/ ← Favicons, og-image, site.webmanifest
├── netlify/
│ └── edge-functions/
│ └── api.ts ← Auto-generated by bini-router on build — do not edit
├── bini.config.ts
├── vite.config.ts
└── package.jsonScripts
| Command | Description |
|---|---|
| npm run dev | Start Vite dev server with HMR |
| npm run build | Bundle for production into dist/ + generate Netlify edge function |
| npm run export | Static SPA export (vite build --mode export) |
| npm start | Serve the production build via bini-server |
| npm run preview | Preview the production build via Vite |
| npm run type-check | TypeScript type check (TS projects only) |
| npm run lint | Lint with ESLint |
File-Based Routing
bini-router maps page.tsx files to URLs — pure SPA, no server required at runtime.
src/app/
page.tsx → /
about.tsx → /about (file-based, no folder needed)
dashboard/
layout.tsx → wraps /dashboard and all children
page.tsx → /dashboard
[id]/
page.tsx → /dashboard/:id
blog/
[slug]/
page.tsx → /blog/:slug
not-found.tsx → custom 404 (optional)Pages
export default function Dashboard() {
return <h1>Dashboard</h1>
}Dynamic routes
// src/app/blog/[slug]/page.tsx
import { useParams } from 'react-router-dom'
export default function Post() {
const { slug } = useParams()
return <h1>Post: {slug}</h1>
}Layouts
Root layouts use {children} — they wrap from outside the router.
Nested layouts use <Outlet /> — they are React Router route wrappers.
// src/app/layout.tsx — root layout
export const metadata = {
title : 'My App',
description: 'Built with Bini.js',
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return <>{children}</>
}// src/app/dashboard/layout.tsx — nested layout
import { Outlet } from 'react-router-dom'
export default function DashboardLayout() {
return (
<div>
<aside>Sidebar</aside>
<main><Outlet /></main>
</div>
)
}Note: Root layouts that render an
<html>tag are automatically skipped bybini-router— they are treated as HTML shell layouts and excluded from the route wrapping chain.
Metadata
Export metadata from any layout.tsx. All fields are optional. Metadata is stripped from the browser bundle at build time — it never ships to the client.
export const metadata = {
title : 'Dashboard',
description: 'Your personal dashboard',
themeColor : '#00CFFF',
manifest : '/site.webmanifest',
openGraph : {
title : 'Dashboard',
url : 'https://myapp.com/dashboard',
images: [{ url: '/og-image.png', width: 1200, height: 630 }],
},
twitter: {
card : 'summary_large_image',
creator: '@yourhandle',
images : ['/og-image.png'],
},
icons: {
icon : [{ url: '/favicon.svg', type: 'image/svg+xml' }],
apple: [{ url: '/apple-touch-icon.png', sizes: '180x180' }],
},
}Per-route titles can also be set from nested layout.tsx files — bini-router reads the title field and injects a <TitleSetter> that updates document.title when the route is active.
API Routes
Create files in src/app/api/. Both plain function handlers and Hono apps are supported. The same files run in dev (Vite middleware), production (bini-server), and on Netlify Edge (auto-generated edge function).
src/app/api/
hello.ts → /api/hello
users.ts → /api/users
posts/
index.ts → /api/posts
[id].ts → /api/posts/:id
[...catch].ts → /api/* catch-allPlain function handler
// src/app/api/hello.ts
export default function handler(req: Request) {
return Response.json({ message: 'hello', method: req.method })
}Hono app
// src/app/api/users.ts
import { Hono } from 'hono'
const app = new Hono().basePath('/api')
app.get('/users', (c) => c.json({ users: ['alice', 'bob'] }))
app.post('/users', async (c) => {
const body = await c.req.json()
return c.json({ created: body }, 201)
})
export default appDynamic API routes
// src/app/api/posts/[id].ts
import { Hono } from 'hono'
const app = new Hono().basePath('/api')
app.get('/posts/:id', (c) => c.json({ id: c.req.param('id') }))
export default appHow API routing works
In dev, bini-router registers a Vite middleware that intercepts /api/* requests, scans your src/app/api/ directory, matches the route, and calls the handler — with hot module replacement on every save.
On build, bini-router auto-generates netlify/edge-functions/api.ts. Hono apps are detected by their import and merged via app.route() — a single shared Hono instance, no duplicate module loading. Plain function handlers are wrapped with app.all().
Plugin System
Bini.js has an official plugin system. Plugins let you extend the framework with reusable behaviour — hook into the dev server, build pipeline, routing, and API layer without touching your app code.
Using plugins
Install a plugin and register it in bini.config.ts:
// bini.config.ts
import { defineConfig } from 'bini-router'
import myPlugin from 'bini-plugin-example'
export default defineConfig({
plugins: [
myPlugin({ /* options */ }),
],
})Multiple plugins are supported and run in order:
export default defineConfig({
plugins: [
authPlugin(),
analyticsPlugin({ trackPageViews: true }),
i18nPlugin({ locales: ['en', 'si'] }),
],
})Writing a plugin
A Bini.js plugin is a function that returns a plugin object:
import type { BiniPlugin } from 'bini-router'
export default function myPlugin(options = {}): BiniPlugin {
return {
name: 'bini-plugin-my-plugin',
onDevStart({ server }) {
console.log('Dev server ready')
},
onBuildStart({ config }) {
console.log('Building...')
},
onBuildEnd({ outDir }) {
console.log(`Built to ${outDir}`)
},
onApiSetup({ app }) {
app.get('/api/my-plugin/status', (c) => c.json({ ok: true }))
},
}
}All lifecycle hooks are optional — implement only what your plugin needs.
Available hooks
| Hook | When it runs | Arguments |
|---|---|---|
| onDevStart | Dev server ready | { server, config } |
| onBuildStart | Before vite build | { config } |
| onBuildEnd | After vite build | { outDir, config } |
| onApiSetup | API router initialised | { app: Hono } |
| onRequest | Every incoming request | { req, next } |
Official plugins
| Plugin | Description |
|---|---|
| bini-plugin-auth | Session-based and JWT authentication |
| bini-plugin-i18n | Internationalisation and locale routing |
| bini-plugin-analytics | Privacy-first page view analytics |
Community plugins follow the
bini-plugin-*naming convention. See the plugin registry for the full list.
Production Server
bini-server is a zero-dependency production server — pure Node.js http, no Express, no Fastify.
npm run build # vite build → dist/
npm start # bini-serverTerminal output:
ß Bini.js (production)
➜ Environments: .env, .env.production
➜ Local: http://localhost:3000/
➜ Network: http://192.168.1.5:3000/Request flow:
Request
├─ /api/* → src/app/api/ handlers (Hono apps or plain functions)
├─ /* → stream static file from dist/
└─ /* → dist/index.html (SPA fallback)| Feature | vite preview | bini-server |
|---|---|---|
| Serves dist/ | ✅ | ✅ |
| API routes | ✅ | ✅ |
| SPA fallback | ✅ | ✅ |
| Production use | ❌ not recommended | ✅ |
| Body timeout | ❌ | ✅ 30s |
| Body size limit | ❌ | ✅ 10 MB |
| Handler timeout | ❌ | ✅ 30s |
| Graceful shutdown | ❌ | ✅ |
| Zero dependencies | ✅ | ✅ |
Override the default port via environment variable:
PORT=8080 npm startDeployment
Netlify (default)
Every Bini.js project is pre-configured for Netlify out of the box. Running vite build automatically generates netlify/edge-functions/api.ts — no extra setup needed.
# netlify.toml
[build]
command = "vite build"
publish = "dist"
[[edge_functions]]
path = "/api/*"
function = "api"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200Use
[[edge_functions]]for the API — not[[redirects]].
Node.js servers (Railway, Render, Fly.io, VPS)
npm run build
npm startRailway and Render inject PORT automatically. Use pm2 on a VPS:
npm install -g pm2
pm2 start "npm start" --name my-app
pm2 save && pm2 startupCloudflare Workers
npm install wrangler// vite.config.ts
biniroute({ platform: 'cloudflare' })vite build && npx wrangler deployVercel
⚠️ Vercel is not yet supported. We are actively working on a Vercel adapter — follow the issue for updates.
Other platforms
bini-router supports netlify · cloudflare · node · deno · bun · aws. Set platform in vite.config.ts once — vite build generates the correct platform entry file automatically.
| Platform | Output file |
|---|---|
| netlify | netlify/edge-functions/api.ts |
| cloudflare | worker.ts / worker.js |
| node | server/index.ts / server/index.js |
| deno | server/index.ts / server/index.js |
| bun | server/index.ts / server/index.js |
| aws | handler.ts / handler.js |
Environment Variables
bini-env detects and displays active .env files on every server start (dev and preview):
ß Bini.js (dev)
➜ Environments: .env.local, .env
➜ Local: http://localhost:3000/
➜ Network: http://192.168.1.10:3000/Files are checked in priority order: .env.local → .env.[mode].local → .env.[mode] → .env
Prefix variables with VITE_ to expose them to the browser:
# .env
VITE_API_URL=https://api.example.com
# .env.local (overrides .env, git-ignored)
VITE_API_URL=http://localhost:4000Dev Overlay
bini-overlay adds an animated Bini.js logo badge to the bottom-left corner during development. It has three states:
| State | Behaviour | |---|---| | Loading | Logo draws itself with a stroke animation and pulses | | Idle | Logo sits quietly as a filled gradient icon | | Error | Badge hides — full error overlay with source maps takes over |
The badge recovers automatically when an error is fixed and HMR fires. Never appears in production builds.
# Disable without touching config
DISABLE_BINI_OVERLAY=true npm run devPowered By
| Package | Role |
|---|---|
| bini-router | File-based routing, nested layouts, metadata, Hono API routes, multi-platform deployment |
| bini-server | Zero-dependency production server |
| bini-overlay | Dev animated logo badge + error overlay |
| bini-env | Clean dev/preview startup banner |
| bini-export | Static SPA export for GitHub Pages, Netlify static, S3 |
| hono | API route handler runtime |
| vite | Dev server and production bundler |
| react | UI library |
| react-router-dom | Client-side routing |
Resources
- Website: https://bini.js.org
- GitHub: https://github.com/Binidu01/bini-cli
- npm: https://www.npmjs.com/package/create-bini-app
- Issues: https://github.com/Binidu01/bini-cli/issues
MIT © Binidu Ranasinghe
