Drop a file in src/api/ and it's a route. Every CPU core serves traffic by
default. Heavy work gets its own queue. One ignite() call. No ceremony.
$ npx create-efc-app my-api $ cd my-api && efc start dev ✔ Worker 1 ready on :3000 ✔ Worker 2 ready on :3000 ✔ Worker 3 ready on :3000 ✔ Worker 4 ready on :3000 → GET /health src/api/health.ts → GET /users src/api/users/index.ts → POST /users src/api/users/index.ts → GET /users/:id src/api/users/[id].ts
Most Express apps end up with the same three pain points. EFC solves all three at the convention level.
src/api/users/[id].ts becomes /users/:id. No router files,
no app.use() chains. Add a file, get a route. Rename it, the URL renames
too.
Node runs on one core by default. EFC forks one worker per CPU at startup, lets the OS distribute connections, and respawns crashed workers before you notice.
Sending an email or resizing an image shouldn't block a response. Files in
src/tasks/ run off the request path — in a queue, with retries,
optionally in their own thread.
You choose http-only cookies or Bearer tokens once, during
create-efc-app. A real JWT_SECRET lands in
.env automatically.
export const middlewares = [requireAuth] is all protection takes.
defineModel gives you identical .find(),
.create(), .delete() calls on MongoDB today —
PostgreSQL is scaffoldable but the adapter itself lands in Phase 2.
efc doctor ships with it
Run efc doctor to check config, env vars, DB connectivity, and worker
setup before you deploy. efc routes prints the resolved route table if
something looks wrong.
Pick Gmail and the scaffolder preconfigures
smtp.gmail.com — no host or port to type. It walks you straight to
generating a 16-character App Password instead of your real one,
and wires a working nodemailer task on the other end.
Toggle RBAC at scaffold time and every protected route swaps in
requireRole(...roles) automatically, backed by a real Role
model — instead of an inline if (user.role !== 'admin') check.
Enable User and Admin portal and get 15+ models — sessions, notifications, files, subscriptions, invoices, coupons, FAQs — and 80+ working routes to match. Not stubs: real auth, billing, and content endpoints.
EFC walks src/api/ at boot and registers every file as a route.
[id].ts → :id. index.ts → the directory
path. Unregistered HTTP verbs get a 405 back — you don't write
that logic.
export const middlewares = [...]compose()import type { Request, Response } from 'express'; import { User } from '../../model/User.js'; import { HttpError } from 'express-file-cluster'; export const GET = async (req: Request, res: Response) => { const user = await User.findById(req.params.id); if (!user) throw new HttpError(404, 'User not found'); res.json(user); }; export const DELETE = async (req: Request, res: Response) => { await User.delete(req.params.id); res.status(204).send(); };
A file in src/tasks/ is a named job. Call
enqueue('SendEmail', payload) from any handler and return
immediately. EFC picks the right execution model — event loop for I/O,
worker_threads for CPU.
schedule in defineTask (planned, not yet executed)import { defineTask } from 'express-file-cluster/tasks'; // Task name = filename export default defineTask<{ to: string; subject: string }>( async (payload) => { await mailer.send(payload); } ); // Trigger from a route — respond immediately export const POST = async (req, res) => { const user = await User.create(req.body); await enqueue('SendEmail', { to: user.email }); res.status(202).json({ id: user.id, queued: true }); };
Answer one question during scaffolding: SSR app or SPA? EFC writes the
login/logout route stubs, sets the cookie flags, generates a real
JWT_SECRET, and puts requireAuth in scope. You protect
a route with a single export.
.envimport { issueToken, requireAuth } from 'express-file-cluster/auth'; export const POST = async (req, res) => { const user = await verifyCredentials(req.body); issueToken(res, { sub: user.id, role: user.role }); res.json({ message: 'Logged in' }); }; // src/api/users/index.ts — protect with one line export const middlewares = [requireAuth]; export const GET = async (req, res) => { res.json(await User.find()); };
Choose Gmail and the scaffolder skips host/port entirely —
smtp.gmail.com is preconfigured. It then walks you to generating
a 16-character App Password and rejects anything that isn't
one, so you don't discover the hard way that Google blocks real passwords over
SMTP.
nodemailer transport, not a stubimport { defineTask } from 'express-file-cluster/tasks'; import nodemailer from 'nodemailer'; // .env — SMTP_PASS must be a 16-char Gmail App Password, // not your normal Gmail login password const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST, port: Number(process.env.SMTP_PORT ?? 587), secure: Number(process.env.SMTP_PORT) === 465, auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS }, }); export default defineTask(async (payload) => { await transporter.sendMail({ from: process.env.SMTP_FROM ?? process.env.SMTP_USER, to: payload.to, subject: payload.subject, html: payload.body, }); });
ignite() — one entry point
Pass cluster and tasks. Everything else — port, database,
auth, secrets, CORS — is read from .env and efc.config.ts.
EFC runs the Pre-Flight sequence on every worker: connect → configure auth →
scan routes → register tasks → listen.
os.cpus().lengthdev mode: single process, hot reload, source mapsimport { ignite, gracefulShutdown } from 'express-file-cluster'; // PORT, DATABASE_URL, JWT_SECRET, CORS_ORIGINS → read from .env // authStrategy, globalMiddlewares → efc.config.ts ignite({ cluster: true, tasks: { backend: 'bullmq' }, }).then(gracefulShutdown).catch(console.error);
defineModel field defaults now accept sentinel operator codes resolved
fresh per document instead of a static value: '$now', '$uuid',
'$objectId', '$timestamp', '$shortId', and
'$currentUser' / '$currentUser.<key>' — the latter reads
the authenticated request's JWT payload via a new AsyncLocalStorage-backed
context, exposed as getCurrentUser() from
express-file-cluster/auth. A new sequence field option adds
real auto-incrementing counters (backed by an atomic, upsert-safe internal
collection) for cases default can't handle, since it resolves
synchronously. defineModel also takes an optional third
ModelOptions argument to control mongoose's timestamps
behavior per model instead of it being hardcoded on.
Also fixed two CLI bugs found while reviewing Phase 1: efc doctor was
checking process.env directly and reporting DATABASE_URL/
JWT_SECRET as missing even when they were set in .env, since
nothing had loaded it into the process; and efc start dev could silently
fail to force development mode if the parent shell already exported
NODE_ENV=production, undermining the documented single-process dev
guarantee.
The create-efc-app wizard now lets you toggle individual sub-features
within the User and Admin portals — analytics, billing, support tickets, content
management, and more — instead of an all-or-nothing switch, and only scaffolds the
models and routes each selection actually needs. express also moved to
a peer dependency so your project controls its own Express version.
On the framework side, EFCConfig gained a requestTimeout
option (milliseconds) — requests that run longer are terminated with a
408 instead of hanging indefinitely — and HttpError now
implements toJSON(), so it serializes consistently whether it's caught
by the global error interceptor or passed straight to res.json().
RouteMeta is now keyed by HTTP method —
{ GET: { description, request?, response? }, PUT: { ... } } — so each
verb on a multi-method route gets its own doc block in the dashboard instead of
sharing one flat description. The new RouteMethodMeta interface is
exported from express-file-cluster.
The dashboard renderer was updated to show one collapsible block per implemented
method. All 76 routes in the example app and every template in
create-efc-app were converted — including the full CRUD generators for
admin/content/* and admin/billing/*. Routes that previously
had no meta at all (sessions, notifications, files, favorites,
bookmarks, api-keys, roles, billing, admins) now have real per-method examples.
forgot-password, reset-password, verify-email,
and refresh now ship with real MongoDB-backed logic instead of
// TODO stubs — token generation, expiry checks, and a rotating,
DB-stored refresh token — in both the example app and everything
create-efc-app scaffolds. register and login
were updated to issue the verification and refresh tokens these routes consume.
requireAuth also gained a role-check shorthand:
requireAuth('admin') now verifies the JWT and checks the role in
one call, replacing the old separate requireRole middleware pattern
everywhere — see RBAC.
When Mailer is also enabled, the reset/verification routes call
enqueue('SendEmail', ...) automatically.
The mailer setup no longer asks for a raw SMTP host/port. Pick Gmail
and smtp.gmail.com is preconfigured automatically — you're walked
straight to generating a 16-character App Password, and the wizard rejects
anything that isn't one. Picking Other still lets you enter a
custom SMTP host and port.
Added a companion MCP
server (mcp/) exposing this documentation as 10 resources, 12 tools,
and 6 prompts to MCP-compatible AI assistants — scaffold routes/tasks, resolve
file-to-URL mappings, and check known EFC gotchas without leaving your editor.
Added a live API documentation page (set dashboard: true) that
auto-generates from route meta exports in development. Response bodies
render as type names. Added basePath config option and fixed
NODE_ENV to be a default overridable by .env.
Removed apiDir and tasksDir configurations to strictly
enforce directory conventions. Scaffolding tool now automatically generates standard
admin and user dashboards, alongside default authentication endpoints.
Migrated to native async Web Crypto via jose, removed legacy dotenv runtime execution, and replaced chalk with picocolors for faster CLI performance.
Added efc doctor diagnostics, efc routes, and efc tasks commands. Stabilized cluster scaffolding with proper .env injection and integrated local file change monitoring.
Added automated NPM publishing via GitHub Actions and fixed production build configuration issues to ensure seamless developer experience for new projects.
Initial core MVP framework release featuring zero-boilerplate file-based routing, multi-core CPU clustering, authentication adapters, and background task processing.
npx create-efc-appFile-based routing, multi-core clustering, auth, background tasks, the scaffolder, and a live per-method API dashboard are all shipped. Phase 2 — PostgreSQL adapter, Zod validation, and cron scheduling — is next. Come build it.