create-bini-app
v10.0.2
Published
Build full-stack React apps for web, desktop, and mobile — from one codebase.
Maintainers
Keywords
Readme
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXz
YXXXXXXXXXXXXXXXXXXXXXXXX
vXXXXXXXXXXn YXXXXXXXXXX
XXXXXXXXX XXXXXXXXXX
XXXXXXXXX XXXXXXXXXX
XXXXXXXXX XXXXXXXXXXXXXX
XXXXXXXXX XXXXXXXXXXXYz
XXXXXXXXX XXXXXXXXXXXXXXXXY
XXXXXXXXX YXXXXXXXXXXXXX
XXXXXXXXX YXXXXXXXXX
XXXXXXXXX XXXXXXXXXz
XXXXXXXXX Xn YXXXXXXXXXX
XXXXXXXXX XXXXXXXXXXXXXXXXXX
XXXXXXXXX XXXXXXXXXXXXXXXX
XXXXXXXXX XXXXXXXXXXXXX Build full-stack React apps for web, desktop, and mobile — from one codebase.
Quick Start
npx create-bini-app@latest my-app
cd my-app
npm install
npm run devThat's it — http://localhost:3000 opens automatically.
Why Bini.js
Most React starters give you a bundler and call it a day. Bini.js gives you a framework: file-based routing, a backend, environment handling, a native app story, and a deployment CLI, wired together and configured correctly from the first commit.
- One codebase, every target. The same routes, API handlers, and components compile to a web app, a desktop binary, or a mobile app. Nothing is emulated or wrapped — desktop and mobile builds are real Tauri apps.
- Convention over configuration. Drop a file in
src/app/, get a route. Drop a file insrc/app/api/, get an endpoint. No router config, no manual code splitting, no boilerplate. - Fast by default. Vite + Rolldown for bundling, Oxlint + Oxfmt for linting and formatting — all Rust-based, all pre-configured.
Features
Framework
- File-based routing with nested layouts, dynamic segments, and per-route metadata
.mdxand.mdcontent routes work out of the box alongside.tsx/.jsx— no extra setup- Folder-scoped
loading.tsx,not-found.tsx, anderror.tsx— the nearest one up the folder tree wins, same mental model as layouts - Automatic code splitting — every route is lazy-loaded with no
React.lazy()boilerplate - Auto-imports for common React, React Router, and env hooks — zero import statements needed
Backend
- API routes powered by Hono — plain functions or full Hono apps, colocated in
src/app/api/ - One handler, every runtime: Vite dev middleware,
bini-server, or abini-deploy-generated edge function — no code changes required between them - Environment variable loading with startup banners, across Node.js, Bun, Deno, and edge runtimes
Native
- Desktop and mobile builds via Tauri — Windows, macOS, Linux, Android, and iOS from the same project
- Native plugin wiring generated automatically from the web APIs you actually call
- Code signing, bundle identifiers, and app icons handled at scaffold time
Developer Experience
- Animated dev overlay with Shiki-highlighted error panels and clickable stack frames
- TypeScript or JavaScript, Tailwind CSS v4, CSS Modules, or no styling — your choice at scaffold time
- Oxlint + Oxfmt — 50–100× faster than ESLint and Prettier, pre-configured out of the box
Deployment
bini-deploy— a bundled CLI (npm run deploy) that generates hosting config for your chosen platform and pushes the project to GitHub- Static export with smart
404.htmlfallback for any static host - Hosting config generation for Netlify Edge Functions, Vercel, Cloudflare Workers, Node.js, and Deno
Requirements
| | |
|---|---|
| Node.js | >= 20.19.0 (required by Vite 8) |
CLI
npx create-bini-app@latest # interactive
npx create-bini-app@latest my-app # skip the name prompt
npx create-bini-app@latest my-app --typescript --tailwind --platform macos| Flag | Description |
|---|---|
| --typescript / --javascript | Language |
| --tailwind / --css-modules / --none | Styling |
| --platform <target> | web (default) · windows · macos · linux · android · ios |
| --app-name <name> | Display name — desktop/mobile app name and window title |
| --sign / --nosign | Auto-confirm or skip code-signing setup |
| --npm / --pnpm / --yarn / --bun | Force a package manager instead of auto-detecting |
| --install / --no-install | Install dependencies without prompting |
| --force | Overwrite an existing directory |
| --version, -v / --help, -h | Print version / show help |
In non-interactive environments (CI, no TTY), prompts are skipped and defaults apply: TypeScript, Tailwind, web platform. Flags always override the defaults.
Project Structure
my-app/
├── src/
│ ├── app/
│ │ ├── api/ ← API route handlers
│ │ │ └── hello.ts → /api/hello
│ │ ├── layout.tsx ← Root layout + metadata
│ │ ├── page.tsx ← /
│ │ ├── not-found.tsx ← Custom 404 (optional, folder-scoped)
│ │ ├── loading.tsx ← Custom loading screen (optional, folder-scoped)
│ │ ├── error.tsx ← Custom error boundary (optional, folder-scoped)
│ │ └── globals.css
│ ├── main.tsx ← Entry point
│ └── App.tsx ← Generated by bini-router — don't edit
├── public/
│ ├── favicon.ico
│ ├── apple-touch-icon.png
│ ├── og-image.png
│ ├── logo.png ← Source icon for native app icons
│ └── site.webmanifest
├── .oxlintrc.json
├── .oxfmtrc.json
├── vite.config.ts
└── package.jsonnot-found.tsx, loading.tsx, and error.tsx aren't scaffolded by default, but bini-router picks them up anywhere in src/app/ if you add them — see Routing.
Scripts
| Command | Description |
|---|---|
| dev | Start the Vite dev server with HMR |
| build | Type-check, then bundle for production |
| preview | Preview the production build |
| type-check | tsc --noEmit |
| lint / format / check | Oxlint / Oxfmt / both + type-check |
| deploy | Run bini-deploy — generate hosting config (web) or just push (native), then push to GitHub |
| export (web only) | Static SPA export |
| start (web only) | Serve the production build via bini-server |
| tauri:dev / tauri:build (desktop only) | Run or build the desktop app |
| android / android:build, ios / ios:build (mobile only) | Run or build the mobile app |
deploy is present in every scaffold, regardless of platform — see Deployment for exactly what it does on each target. export and start are web-only; a desktop or mobile scaffold never sees them. See Platforms for the full command and requirement breakdown per target.
Routing
bini-router maps files in src/app/ to URLs. No router config, no manual React.lazy() — every route is code-split automatically.
src/app/
page.tsx → /
about.tsx → /about
about.mdx → /about (MDX also works for pages/content routes)
dashboard/
layout.tsx wraps /dashboard and its children
page.tsx → /dashboard
[id]/page.tsx → /dashboard/:id
not-found.tsx custom 404 (optional, folder-scoped)
loading.tsx custom loading screen (optional, folder-scoped)
error.tsx custom error boundary (optional, folder-scoped)// src/app/dashboard/[id]/page.tsx — useParams is auto-imported, no import needed
export default function Dashboard() {
const { id } = useParams();
const [count, setCount] = useState(0);
return <h1>Dashboard {id}: {count}</h1>;
}Layouts — root and nested — render children through <Outlet />:
// src/app/dashboard/layout.tsx
export default function DashboardLayout() {
return (
<div>
<aside>Sidebar</aside>
<main><Outlet /></main>
</div>
);
}Export metadata from any layout to control <title>, Open Graph, Twitter cards, and icons. Root metadata is injected into index.html at build time; nested metadata updates document.title at runtime. It never ships to the client bundle.
export const metadata = {
title: 'Dashboard',
description: 'Your personal dashboard',
openGraph: { images: [{ url: '/og-image.png', width: 1200, height: 630 }] },
};Layouts with an
<html>tag, or without a default export, are excluded from the route chain automatically.layout.tsx,not-found.tsx,loading.tsx, anderror.tsxmust be.tsx/.jsx/.ts/.js— MDX isn't supported for these, since they define app structure rather than content.pagefiles and other flat/content routes support.mdx/.mdin addition.
Folder-scoped boundaries
loading.tsx, not-found.tsx, and error.tsx all use the same "nearest wins" resolution as layouts: a file in a subfolder only affects that subfolder, and shadows (without deleting) the same file in any ancestor folder. A route without a closer match falls through to the nearest ancestor that has one, all the way up to the root, and to a built-in default if nothing exists anywhere.
// src/app/dashboard/error.tsx — only catches errors thrown inside /dashboard/*
export default function DashboardError({ error, reset }: { error: Error; reset: () => void }) {
return (
<div>
<h2>Something broke in the dashboard</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
);
}API Routes
Files in src/app/api/ become endpoints. Export a plain function or a Hono app — both work identically in dev and in production.
// src/app/api/hello.ts — plain handler
export default function handler(req: Request) {
return Response.json({ message: 'hello', method: req.method });
}// src/app/api/users/[id].ts — Hono app, dynamic segment
import { Hono } from 'hono';
const app = new Hono();
app.get('/users/:id', (c) => c.json({ id: c.req.param('id') }));
export default app;getEnv and requireEnv are auto-imported for reading environment variables server-side — no dotenv setup needed.
How it routes: in dev and preview, a Vite middleware matches /api/* against src/app/api/ with HMR on every save. In production on the node target, bini-server reads the same handlers directly at request time — no build step, no generated entry file. For other hosting targets, running bini-deploy (via npm run deploy) generates the platform-specific entry file that merges your Hono apps into one instance (netlify/edge-functions/api.ts, api/index.ts, etc.); plain handlers are wrapped automatically. In packaged desktop/mobile apps, the same Hono instance runs in-memory in the WebView — no server process, no code changes required.
Environment Variables
bini-env loads .env files automatically and prints which ones are active on every dev/preview start, in priority order: .env.local → .env.[mode].local → .env.[mode] → .env.
# .env
BINI_PUBLIC_API_URL=https://api.example.com # client-side: import.meta.env.BINI_*
[email protected] # server-side: requireEnv() in API routesimport.meta.env.BINI_PUBLIC_API_URL // client code
const user = requireEnv('SMTP_USER'); // API routes — throws if missing, auto-imported⚠️ Never put secrets in
BINI_*orVITE_*variables — both prefixes are exposed to the browser bundle.
Production Server
Web target only. bini-server is a zero-dependency Node.js server — no Express, no Fastify — that serves dist/, proxies /api/* to your Hono handlers, and adds what vite preview doesn't: ETag/304 caching, graceful shutdown, body/handler timeouts, and automatic port fallback.
npm run build
npm start # PORT=8080 npm start to override the portOverride the default directories if needed: BINI_API_DIR (default src/app/api), BINI_DIST_DIR (default dist).
Static Export
Web target only. bini-export pre-renders every static route to its own index.html and strips server-only files, producing a dist/ that works on any static host.
npm run export # vite build --mode exportDynamic routes aren't pre-rendered — they resolve client-side via a generated 404.html: a copy of index.html if you have a custom not-found.tsx, otherwise a redirect script that preserves the path through sessionStorage. Works out of the box on GitHub Pages, Netlify, Vercel, Cloudflare Pages, S3 + CloudFront, Firebase Hosting, and Surge.
Platforms
One codebase, six targets. --platform picks which one you scaffold; each gets exactly the dependencies, scripts, and config it needs — nothing more. Every target also gets a deploy script for pushing the project to GitHub via bini-deploy — see Deployment for what it does on each one.
npx create-bini-app@latest my-app --platform macos
npx create-bini-app@latest my-app --platform android --app-name "My App" --nosignWeb
Overview — the default target. A standard Vite + React SPA with file-based routing and a Hono API layer.
Features
- Zero-dependency production server (
bini-server) and static export (bini-export) bini-deploygenerates hosting config for Netlify Edge, Vercel, Cloudflare Workers, Node.js, or Deno and pushes to GitHub
Commands
| Command | Description |
|---|---|
| npm run dev | Dev server with HMR |
| npm start | Serve the production build |
| npm run export | Static export |
| npm run deploy | Generate hosting config for your chosen platform and push to GitHub |
Requirements — Node.js >= 20.19.0. Nothing else.
Windows
Overview — a native desktop binary running inside WebView2.
Features
- Filesystem, clipboard, notifications, and dialogs via
@tauri-apps/api, auto-wired bybini-native - Authenticode code signing, configurable at scaffold time or later
Commands
| Command | Description |
|---|---|
| npm run tauri:dev | Run in development mode |
| npm run tauri:build | Build a distributable binary |
| npm run tauri:icon | Regenerate icons from public/logo.png |
| npm run deploy | Push the project to GitHub via bini-deploy |
deploy does not build, sign, or package the binary itself — run tauri:build first, then deploy to push source and any generated config to GitHub.
Requirements
- Microsoft C++ Build Tools (download) — install with "Desktop development with C++"
- Microsoft Edge WebView2 Runtime (download)
macOS
Overview — a native desktop binary running inside WKWebView.
Features
- Same
@tauri-apps/apiaccess as Windows/Linux, auto-wired bybini-native - Ad-hoc signing for local runs, or Developer ID + notarization for distribution
Commands
| Command | Description |
|---|---|
| npm run tauri:dev | Run in development mode |
| npm run tauri:build | Build a distributable binary |
| npm run tauri:icon | Regenerate icons from public/logo.png |
| npm run deploy | Push the project to GitHub via bini-deploy |
deploy does not build, sign, or notarize the app itself — run tauri:build (and your notarization step) first, then deploy to push to GitHub.
Requirements
- Xcode Command Line Tools —
xcode-select --install - Homebrew, then
brew install gtk+3 webkit2gtk pkg-config - Full Xcode (Mac App Store) if you also plan to build for iOS
Linux
Overview — a native desktop binary running inside WebKitGTK, distributed as an AppImage.
Features
- Same
@tauri-apps/apiaccess as Windows/macOS, auto-wired bybini-native - GPG-signed AppImage output, configurable at scaffold time or later
Commands
| Command | Description |
|---|---|
| npm run tauri:dev | Run in development mode |
| npm run tauri:build | Build a distributable AppImage |
| npm run tauri:icon | Regenerate icons from public/logo.png |
| npm run deploy | Push the project to GitHub via bini-deploy |
Requirements — one of:
# Debian/Ubuntu
sudo apt install -y libwebkit2gtk-4.0-dev build-essential libssl-dev \
libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev libxdo-dev pkg-config
# Fedora
sudo dnf install webkit2gtk4.0-devel openssl-devel gtk3-devel \
libappindicator-gtk3-devel librsvg2-devel libxdo-devel pkg-config
# Arch
sudo pacman -S webkit2gtk base-devel openssl gtk3 libappindicator-gtk3 librsvg libxdo pkg-configAndroid
Overview — a real native APK/AAB via Tauri's Android backend, not a WebView wrapper.
Features
- Camera, filesystem, notifications, and geolocation auto-wired by
bini-native - Back button, status bar, and splash screen configurable in
src-tauri/gen/android - Release signing (keystore +
keystore.properties), configurable at scaffold time or later
Commands
| Command | Description |
|---|---|
| npm run android | Run on an emulator or connected device |
| npm run android:build | Build a release APK/AAB |
| npm run deploy | Push the project to GitHub via bini-deploy |
deploy does not build a signed APK or submit to the Play Store — run android:build first, then deploy to push to GitHub. Store submission is manual.
Requirements
- Java JDK 17 (
JAVA_HOMEset) — download - Android Studio, with SDK/Build Tools/NDK (
ANDROID_HOMEset) — download - Rust targets:
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
iOS
Overview — a real native app via Tauri's iOS backend, running inside WKWebView. macOS + Xcode required.
Features
- Same native plugin wiring story as Android
- Xcode-managed automatic signing for local runs; manual certificate/profile signing for CI
Commands
| Command | Description |
|---|---|
| npm run ios | Run on the Simulator or a connected device |
| npm run ios:build | Build the app |
| npm run deploy | Push the project to GitHub via bini-deploy |
deploy does not build, sign, or submit to TestFlight/App Store — those remain manual (or CI-driven) steps; deploy only pushes the project source to GitHub.
Requirements
- Xcode (Mac App Store) + Command Line Tools —
xcode-select --install - CocoaPods —
sudo gem install cocoapods - Rust targets:
rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim
Notes — iOS projects also get a bare "tauri": "npx @tauri-apps/cli" script in package.json. Xcode's build phase calls it directly (npm run tauri -- ios xcode-script ...), bypassing the commands above — builds triggered from inside Xcode fail with Missing script: "tauri" without it. Don't rename or remove it.
Developer Experience
Dev overlay. An animated status badge in the corner reflects loading/idle/error state live. On error, it expands into a Shiki-highlighted code frame with a clickable file link and filtered call stack. Disable with DISABLE_BINI_OVERLAY=true npm run dev.
Auto-imports. These are available in every file under src/app/ (including .mdx/.md) with no import statement:
| From | Symbols |
|---|---|
| react | useState useEffect useRef useMemo useCallback useContext createContext useReducer useId useTransition useDeferredValue |
| react-router-dom | Link NavLink useNavigate useParams useLocation useSearchParams Outlet |
| bini-env | getEnv requireEnv |
A manual import of any of these is detected and injection is skipped — no duplicates.
Linting & formatting. Oxlint and Oxfmt ship pre-configured (.oxlintrc.json, .oxfmtrc.json) — Rust-based, 50–100× faster than ESLint/Prettier, no setup required.
npm run lint # Oxlint
npm run format # Oxfmt
npm run check # both + type-check — CI-readyDeployment
bini-deploy is bundled into every scaffold and exposed as npm run deploy. What it does depends on the platform:
- Web — prompts for a hosting target (
node,netlify,vercel,cloudflare, ordeno), generates that platform's entry file and any hosting config (netlify.toml,vercel.json,wrangler.toml) directly in your project, then commits and pushes everything to the GitHub repository you specify. - Windows / macOS / Linux / Android / iOS — generates no hosting config (there's nothing to host). It commits and pushes the project as-is to GitHub, and prints the manual next-step commands for that platform (clone, install, build). It does not build, sign, notarize, or submit anything to an app store — those remain separate, manual (or CI) steps.
npm run deploy # interactive
npx bini-deploy --platform web --hosting vercel --repo https://github.com/you/app --yes # non-interactive| Platform | File generated |
|---|---|
| netlify | netlify/edge-functions/api.ts + netlify.toml |
| vercel | api/index.ts + vercel.json |
| cloudflare | worker.ts + wrangler.toml |
| deno | server/index.ts |
| node | (none — bini-server reads src/app/api/ directly) |
Vercel and Deno Deploy read their entry file before running your build —
bini-deploycommits the generated file for you, so it's there before CI runs.
Netlify example, generated automatically by bini-deploy when you choose it:
# netlify.toml
[build]
command = "vite build"
publish = "dist"
[[edge_functions]]
path = "/api/*"
function = "api"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200Edge Functions run on Deno, not Node — packages depending on Node built-ins (
fs,nodemailer) won't work there.
Node.js (Railway, Render, Fly.io, a VPS): npm run build && npm start. bini-server reads handlers directly from src/app/api/, so deploy the whole project — not just dist/. Use pm2 on a bare VPS.
GitHub Pages / subpaths — set base: '/my-repo/' in vite.config.ts, then npm run export for a fully pre-rendered, subpath-aware dist/.
Packages
| Package | Role |
|---|---|
| bini-router | File-based routing, layouts, MDX/Markdown pages, folder-scoped loading/not-found/error, metadata, auto-imports, dev/preview API routing |
| bini-deploy | Generates hosting config for web targets and pushes the project to GitHub, for every platform |
| bini-native | Automatic Tauri plugin wiring for desktop and mobile |
| @tauri-apps/cli | Compiles the app to a native Windows/macOS/Linux/Android/iOS binary |
| bini-server | Zero-dependency production server (web only) |
| bini-export | Static SPA export (web only) |
| bini-overlay | Dev error overlay |
| bini-env | Environment variable loading |
| hono | API route runtime |
| vite | Dev server + Rolldown bundler |
| react / react-router-dom | UI + routing |
| oxlint / oxfmt | Linting + formatting |
Most packages install at latest; typescript is pinned to ^6.0.0 (TypeScript 7 shipped without a public compiler API, which breaks several build tools in this stack).
Resources
- bini.js.org — documentation
- GitHub — source
- npm — package
- Issues — bugs & feature requests
MIT © Binidu Ranasinghe
