Bloom is a TypeScript full-stack framework designed for the era where developers and AI coding agents ship software together. UIKit (React components) + AppKit (Node backend) + a tiny scaffolding CLI — cover web, desktop (Electron) and mobile (Capacitor) from one codebase. Every package ships llms.txt + AGENTS.md machine-readable specs so AI agents generate correct code, and developers review work they actually understand.
npm install -g @bloomneo/bloom
# Web app (React + Express, FBCA routing)
bloom create my-app
# Auth + user management + admin panel
bloom create my-app userapp
# Admin console (userapp + audit log, settings dashboard, 3-tier roles)
bloom create my-app adminapp
# Desktop app (Electron, SQLite, cross-platform)
bloom create my-app desktop-basicapp
bloom create my-app desktop-userapp
# Mobile app (iOS + Android via Capacitor 6)
bloom create my-app mobile-basicapp
This page is written for humans. Agents should fetch /bloom/llms.txt and /bloom/AGENTS.md for the authoritative CLI + template reference. Inside a scaffolded project, read the project's root AGENTS.md first, then docs/appkit-agents.md + docs/uikit-agents.md (auto-populated by npm install) for library-specific rules.
Bloom isn't monolithic. Install the full stack with bloom create, or pull any pillar into an existing project. They're designed to compose but work standalone.
Node.js toolkit with 12 typed modules — JWT auth, Prisma multi-tenancy, Redis cache & queue, S3 storage, SMTP/Resend email, structured logging, CSRF & rate limiting, semantic HTTP errors, typed config. Auto-scales memory → Redis → cloud without a rewrite.
45+ typed React components, 5 OKLCH themes, 6 production layouts. Forms, tables, dialogs, toasts, confirms, permission gates, responsive hooks — all wired on Radix + Tailwind. Web, Electron desktop, Capacitor mobile, browser-extension popups.
5 templates (web / auth+users / desktop / desktop+auth / mobile). Feature-Based Component Architecture — add a route by adding a folder. Every scaffold ships docs/ + .claude/skills/ auto-populated from installed packages, kept version-matched on every npm install.
Bloom has one load-bearing convention across every template: Feature-Based Component Architecture. Backend endpoints and frontend pages are auto-discovered by scanning feature folders. No router config to edit, no import list to maintain, no manual registration. Adding functionality = creating files.
# Backend — drop a folder → auto-mounted API route
src/api/features/
└── invoice/
├── invoice.route.ts → /api/invoice
├── invoice.service.ts (business logic; uses appkit)
└── invoice.types.ts (shared with frontend)
# Frontend — drop a folder → auto-routed page
src/web/features/
└── invoice/
├── pages/
│ ├── index.tsx → /invoice
│ ├── [id].tsx → /invoice/:id (dynamic segment)
│ └── [...path].tsx → /invoice/* (catch-all)
├── components/ (local to this feature)
└── services/ (API calls; uses uikit useApi)
Route pattern rules — how file paths become URLs:
| File path | Becomes route |
|---|---|
features/main/pages/index.tsx | / (root) |
features/blog/pages/index.tsx | /blog |
features/blog/pages/[slug].tsx | /blog/:slug |
features/docs/pages/[...path].tsx | /docs/* (catch-all) |
features/invoice/invoice.route.ts | /api/invoice |
Under the hood: the web router uses import.meta.glob('../features/*/pages/**/*.tsx'); the API router scans src/api/features/ at boot. Both are re-discovered on hot reload — no server restart required when you add a feature.
Most frameworks were built for humans and retro-fitted with an LLM integration after ChatGPT. Bloom starts from a different observation: agents rarely fail while researching — they fail mid-sentence, writing something plausible without stopping to check. No document intercepts that. A type does. The defences below are ordered by what actually catches a mistake, not by what reads well in a README.
| What Bloom ships | What it catches |
|---|---|
Generated ApiRoute union | Every path the API serves, derived from the route files and regenerated on install, dev and build. api.get('/api/user') becomes a compile error when the real route is /api/user/admin/users — the most expensive class of mistake, caught before the app runs. |
| One typed API client | shared/api.ts owns the base URL, the auth token and the error shape. No feature hand-rolls fetch, so none can hand-roll it wrong. |
| Gates on the router, not the handler | router.use(auth.requireLoginToken()) once per feature. A handler added next month inherits it instead of having to remember it. |
| Errors that name the cause | A misconfigured API base says so, rather than Unexpected token '<' from a JSON parse three frames away. A route file the router will never mount warns at startup instead of quietly 404ing. |
| Palette lockdown | Raw hex and stock Tailwind colour classes are absent from the build, so an agent cannot drift the design system — the wrong class does not exist. |
Typed errors with module + code + docsUrl | An error that carries the page explaining it, so the next step is a link rather than a guess. |
| Single canonical API pattern | xxxClass.get() across every AppKit module. One shape to learn, one shape to get right. |
llms.txt and AGENTS.md in every package | The reference and the rules, re-synced from node_modules on every install so they cannot drift from the installed API. Genuinely useful — but read during research, which is not when mistakes happen. The rows above are what catch them. |
| Drift-checked public surface (CI) | Every package fails CI if docs, types or template pins disagree with package.json. |
Closest in scope to T3 Stack or RedwoodJS — not Laravel or NestJS directly. Bloom's differentiator is agentic-first design + cross-platform coverage; its weakness is ecosystem age.
| Dimension | Bloom | Laravel | NestJS | T3 Stack | Next.js |
|---|---|---|---|---|---|
| Backend | ✓ AppKit | ✓ (PHP) | ✓ core | Next API | Partial |
| Frontend | ✓ UIKit | Blade | — | ✓ Next | ✓ |
| Desktop (Electron) | ✓ | — | — | — | — |
| Mobile native (Capacitor) | ✓ | — | — | — | — |
| Auth built-in | ✓ JWT + roles | ✓ | via Passport | NextAuth | — |
| Queue / background jobs | ✓ memory→Redis | ✓ Horizon | ✓ Bull | — | — |
Ships llms.txt | ✓ | — | — | — | — |
Ships AGENTS.md | ✓ | — | — | — | — |
| Agent skills | ✓ | — | — | — | — |
| Drift-checked docs (CI) | ✓ | — | — | — | — |
| Postinstall doc hydration | ✓ | — | — | — | — |
| Ecosystem maturity | New | 14 yrs | 7 yrs | 3 yrs | 8 yrs |
| LLM training-data familiarity | Low | High | High | Medium | Very high |
Bloom's bet: runtime llms.txt fetching closes the training-data gap. Agents don't need to have seen Bloom before — the docs ship with the code, version-matched, ready to load into context.
Best fit today: solo developers and small teams (≤10) shipping SaaS with heavy Claude Code / Cursor / Copilot usage, and anyone building cross-platform products (web + desktop + mobile) who doesn't want to stitch three stacks.
Not yet the right fit: 100-engineer enterprise teams where hiring pool and 10-year ecosystem matter more than agentic ergonomics. Use Laravel or NestJS for those.
Bloom is the scaffolder teams reach for when they need a working stack — not an evening evaluating starters.
userapp and ship MVP in a weekend.adminapp out of the box. Add a feature folder and ship.userapp template.desktop-basicapp / desktop-userapp ship Electron + embedded Express + SQLite. Packages to Windows, macOS, Linux.mobile-basicapp via Capacitor 6. Same React codebase, same UIKit components, native builds on both stores.PageLayout + themes. Swap the default template's pages for yours.docs/ and .claude/skills/ are populated on install so agents extend your app correctly.userapp + a few feature folders, no router config to manage.Most ways to break a Bloom project now fail loudly. These are the ones that do not — the residue worth knowing. Point your AI agent at AGENTS.md where they're spelled out line by line.
router.use(auth.requireLoginToken()) belongs at the top of every route file that touches user data. Since 5.2.1 the router warns at startup if one is missing, naming the file — declare a genuinely public surface with export const isPublic = true to silence it.import { ... } from '@bloomneo/bloom'. Bloom is a CLI, not a runtime library. Nothing ships in node_modules for your app to import. If you're writing that import, you're off the canonical path.features/<name>/pages/index.tsx. Don't touch the router file.docs/appkit.md / docs/uikit.md. They're regenerated from node_modules on every npm install. Edit the upstream package's llms.txt, not the copy.bloom create into a non-empty directory. Exits 1 with a conflict error. Use . for the current directory only if it's empty (other than dotfiles).latest. Templates pin @bloomneo/appkit@^4.0.0 and @bloomneo/uikit@^2.0.1 for a reason — coordinated majors need a new bloom release.cd my-app
npm install # installs dependencies AND copies appkit/uikit docs into docs/
npm run dev # starts frontend + backend concurrently
| App type | Frontend | Backend |
|---|---|---|
| Web app | http://localhost:5173 (Vite + React) | http://localhost:3000 (Express + AppKit) |
| Desktop app | Electron window at http://localhost:5183 | http://localhost:3000 (Express + AppKit) |
| Mobile app | Capacitor native shell | External API (configure VITE_API_URL) |
Each layer adds features across the whole stack — API routes, pages, Prisma models, dependencies — and composes with the others. The preset names below are shorthand for a set of layers, and every one still works.
bloom create my-app
bloom create my-app --auth
bloom create my-app --admin
bloom create my-app --desktop
bloom create my-app --mobile
bloom create my-app basicapp
bloom create my-app userapp
bloom create my-app adminapp
Because they compose, combinations that never existed as separate templates now do — an admin console that also ships as a desktop app and a phone app, from a single src/web:
bloom create my-app adminapp --desktop --mobile
my-app/
├── src/
│ ├── api/ # Express backend (AppKit)
│ │ ├── features/
│ │ │ └── welcome/
│ │ │ ├── welcome.route.ts # Auto-mounts at /api/welcome
│ │ │ └── welcome.service.ts
│ │ ├── lib/
│ │ │ └── api-router.ts # FBCA auto-discovery for routes
│ │ └── server.ts
│ └── web/ # React frontend (UIKit)
│ ├── features/
│ │ ├── main/pages/index.tsx # → /
│ │ ├── gallery/pages/index.tsx
│ │ └── welcome/pages/index.tsx
│ ├── shared/
│ │ └── components/
│ └── main.tsx
├── docs/ # Auto-populated after npm install
│ ├── appkit.md # ← node_modules/@bloomneo/appkit/llms.txt
│ ├── appkit-agents.md # ← node_modules/@bloomneo/appkit/AGENTS.md
│ └── uikit.md # ← node_modules/@bloomneo/uikit/llms.txt
├── AGENTS.md # Project-level AI agent conventions
├── .env.example # Environment variable template
├── package.json
└── tsconfig.json
my-app/
├── src/
│ ├── api/features/
│ │ ├── auth/ # Registration, login, email verify, password reset
│ │ │ ├── auth.route.ts
│ │ │ └── auth.service.ts
│ │ ├── users/ # User CRUD, profile, role management
│ │ │ ├── users.route.ts
│ │ │ └── users.service.ts
│ │ └── admin/ # Admin panel APIs
│ │ ├── admin.route.ts
│ │ └── admin.service.ts
│ └── web/features/
│ ├── auth/pages/ # Login, register, forgot-password pages
│ ├── dashboard/pages/ # Main dashboard
│ └── admin/pages/ # Admin user management UI
├── prisma/
│ ├── schema.prisma # User, Role, Session models
│ └── seed.ts # Default admin user
└── .env.example # DATABASE_URL, BLOOM_AUTH_SECRET, etc.
bloom create my-app userapp
cd my-app
npm install
# Configure your .env (copy from .env.example)
# Set DATABASE_URL, BLOOM_AUTH_SECRET, etc.
npx prisma db push # create tables
npm run db:seed # create default admin user
npm run dev # start frontend + backend
my-desktop-app/
├── src/
│ └── desktop/
│ ├── main/ # Electron main process (AppKit + Express backend)
│ │ ├── features/
│ │ │ └── welcome/
│ │ │ ├── welcome.route.ts
│ │ │ └── welcome.service.ts
│ │ ├── lib/
│ │ └── server.ts
│ └── renderer/ # React frontend (UIKit)
│ ├── features/
│ ├── shared/
│ └── main.tsx
├── electron/
│ └── main.js # Electron main process entry
└── package.json
npm run dev # Electron window + Vite hot reload + Express backend
npm run build # production build
npm run preview # preview production build in Electron
# electron-builder packages for your platform automatically:
# macOS → .dmg, Windows → .exe installer, Linux → .AppImage
my-mobile-app/
├── src/
│ └── web/ # React frontend (UIKit)
│ ├── features/
│ │ ├── home/pages/index.tsx
│ │ └── profile/pages/profile.tsx
│ ├── shared/
│ │ └── components/
│ │ └── MobileNav.tsx # Uses UIKit TabBar + MobileLayout
│ └── main.tsx
├── ios/ # Generated Xcode project
├── android/ # Generated Android Studio project
└── capacitor.config.ts
cd my-mobile-app
npm install
npm run dev # Vite dev server (browser preview)
# iOS
npm run mobile:run:ios # iOS Simulator
npm run mobile:build:ios # Build for Xcode
# Android
npm run mobile:run:android # Android Emulator
npm run android:build # Build APK
Every new feature lives in its own folder. No imports to add, no routing files to edit.
# 1. Create the backend feature
mkdir -p src/api/features/invoice
# Add invoice.route.ts (auto-mounts at /api/invoice)
# Add invoice.service.ts (business logic)
# 2. Create the frontend feature
mkdir -p src/web/features/invoice/pages
# Add index.tsx → routes to /invoice
# Add [id].tsx → routes to /invoice/:id
# That's it. Restart the server — no config needed.
// src/api/features/invoice/invoice.route.ts
import { Router } from 'express';
import { authClass, errorClass } from '@bloomneo/appkit';
import { InvoiceService } from './invoice.service';
const router = Router();
const auth = authClass.get();
const error = errorClass.get();
router.get('/', auth.requireLoginToken(), error.asyncRoute(async (req, res) => {
const invoices = await InvoiceService.list(req.user!.userId);
res.json({ invoices });
}));
router.post('/', auth.requireLoginToken(), error.asyncRoute(async (req, res) => {
const invoice = await InvoiceService.create(req.user!.userId, req.body);
res.status(201).json({ invoice });
}));
export default router;
docs/ foldernpm install automatically copies the latest llms.txt and AGENTS.md from installed packages into docs/appkit.md, docs/uikit.md.
AGENTS.mdProject-level AI instructions at the root: feature structure, env vars, always/never rules, and where to look for APIs.
Agents guess — every model does. The generated ApiRoute union, the typed component props and the shared API client turn those guesses into compile errors instead of runtime bugs.
.env| Template | Required env vars |
|---|---|
basicapp |
BLOOM_AUTH_SECRET |
userapp |
BLOOM_AUTH_SECRET, DATABASE_URL, BLOOM_SECURITY_CSRF_SECRET, optionally RESEND_API_KEY for email |
desktop-basicapp / desktop-userapp |
BLOOM_AUTH_SECRET — SQLite path is auto-configured |
mobile-basicapp |
VITE_API_URL — points to your backend API |
llms.txt + AGENTS.md + Claude Code skills from node_modules into the project on every npm install — so docs never drift from installed versions. The CLI itself is tiny: bloom create and bloom start, nothing else.
src/api/features/<name>/*.route.ts auto-mounts at /api/<name>. Frontend: src/web/features/<name>/pages/*.tsx auto-routes via import.meta.glob. Add a feature = create a folder. No manual route registration, no router config to edit. Dynamic segments use [param], catch-all uses [...path].
bloom create, run npm install. The postinstall hook copies appkit + uikit llms.txt + AGENTS.md into docs/, and every Claude Code skill into .claude/skills/. Point your agent at the project root; it will find AGENTS.md first, then docs/appkit-agents.md + docs/uikit-agents.md for library-specific rules. No extra configuration.
desktop-basicapp and desktop-userapp wrap the same web build and API in Electron (desktop-userapp adds auth). mobile-basicapp wraps the same web build with Capacitor 6 for iOS + Android; the API runs on a server the phone can reach. Same UIKit components and the same API across surfaces.
userapp and desktop-userapp templates require a database (Prisma with Postgres or SQLite). basicapp, desktop-basicapp, and mobile-basicapp run without one.
package.json — so agents never read stale guidance.
src/api/features/<name>/<name>.route.ts. For a page, add src/web/features/<name>/pages/index.tsx. FBCA auto-discovers both. There is no bloom add feature command; features are just folders.
llms.txtCanonical CLI + template reference. Commands, templates, exit codes, flags, FBCA rules. View →
AGENTS.mdAlways-do, never-do rules: what Bloom IS / IS NOT, template decision tree, FBCA conventions. View →
docs/Every scaffold's postinstall copies appkit + uikit llms.txt + AGENTS.md into docs/ on every npm install. Agents always read version-matched docs.