Every Express app starts with the same chores: a router, a cluster, a queue, auth, a database layer. EFC does them once, so your app starts at the part that's actually yours.
Express, but your folder
is your router.
Drop a file in src/api/ and it's a route. Clustering,
background jobs and a typed data layer are already set up.
Left: the route dashboard every app serves in development. Right: the project that produced it.
Routing
The file tree is the route table.
EFC reads src/api/ when the server starts. Rename a file and its URL changes with
it.
A file is a route
Export GET, POST and so on. Methods you leave out answer 405 by themselves.
Brackets are params
[id].ts becomes :id. Static paths always win, so /users/me never
reaches /users/:id.
Optional and catch-all
[[id]] is optional. [...slug] takes the rest of the path as an array.
Validated on the way in
export const schema takes zod, valibot or arktype. Bad input gets a 400 before your
handler runs.
// GET /health export const GET = (req, res) => { res.json({ ok: true }) } // POST /health → 405, no code needed
// GET /users/:id import { HttpError } from 'express-file-cluster' export const GET = async (req, res) => { const user = await User.findById(req.params.id) if (!user) throw new HttpError(404, 'Not found') res.json(user) }
// GET /docs/guides/auth export const GET = (req, res) => { req.params.slug // ['guides', 'auth'] res.json(lookup(req.params.slug)) }
import { z } from 'zod' export const schema = { params: z.object({ id: z.string().uuid() }), } // GET /users/42 → 400 { issues: [...] }
Clustering
One worker per CPU core.
In production the primary process forks a worker for each core and replaces any that crash. Try crashing one.
On shutdown, SIGTERM reaches every worker, so open requests and running jobs
finish first.
Background jobs
Reply first. Do the slow work after.
Each file in src/tasks/ is a job. Queue it from a handler and return 202. It runs
on BullMQ or pg-boss, with retries.
import { defineTask } from 'express-file-cluster/tasks' export default defineTask<{ to: string }>( { retries: 3 }, async ({ to }) => { await mailer.send(to) }, ) // in a route handler await enqueue('SendWelcome', { to: user.email }) res.status(202).json({ queued: true })
Data
Describe the model. Get the types.
Types come from the schema, and tables or collections are created on first use.
defineModel runs on MongoDB and PostgreSQL. defineTable adds relations on
PostgreSQL.
export const Post = defineTable('Post', { title: { type: 'string', required: true }, author: { type: 'uuid', ref: 'User' }, tags: { type: 'array', of: 'uuid', ref: 'Tag' }, }) const posts = await Post.findMany({ include: { author: true, tags: true }, take: 20, }) // one SQL query, typed { author: User | null, tags: Tag[] }
export const User = defineModel('User', { email: { type: 'string', required: true, unique: true }, role: { type: 'string', enum: ['user', 'admin'] }, }) type UserRecord = InferRecord<typeof User> await User.find({ role: 'admin' }) // same call on MongoDB or PostgreSQL
Also included
The rest of the boilerplate.
- Auth
- JWT in a cookie or a Bearer token.
requireAuth('admin')checks roles. - Cache
- Redis or in memory, with
cacheResponse()for GET routes. - Logging
- pino, with a request ID on every line.
- Scaffolder
create-efc-appwrites a working app with user and admin routes.- Mailer
- Gmail or any SMTP server, sent from a background job.
- CLI
- Route listing, migrations, seeds and a read-only database browser.
Changelog
Recent releases.
- 0.12.0
Opt-in OpenTelemetry instrumentation for HTTP and background tasks, with W3C parentage, retry/thread propagation, trace log fields and app-owned SDK shutdown after drain. Mixed ESM/CommonJS modules share queue state.
Production hardening for async middleware, explicit HEAD handlers, partial responses, pg-boss payloads, thread and shutdown lifecycles, session revocation and response-cache isolation. Cache hits replay exact JSON bytes; queue deadlines prevent late readiness submissions.
Update all three packages together and upgrade optional queue peers to BullMQ 5.79.1+ or pg-boss 12.38.0+. SDK/exporter setup remains application-owned; in-flight queue acknowledgments can be uncertain, so handlers must be idempotent.
- 0.11.0
Opt-in OpenAPI 3.1 generation from HTTP routes, request validators and metadata, with offline generation and operation overrides. File
WSexports share middleware, authentication, validation and async context, with origin checks, payload limits, heartbeat and graceful draining.Safe pipelined upgrades and pending-handshake shutdown; accurate query encoding, optional bodies and schema references; protected document URLs and unique optional paths. Update all three packages together. Broadcasts remain worker-local; authentication runs at the handshake.
- 0.10.2
Logout invalidates stored refresh credentials; password snapshots and authentication-time revocation prevent stale logins from reviving sessions. Reset links are consumed once and invalidate refresh credentials; custom logout propagates failures, and mixed ESM/CJS middleware shares state. Adds
createTestApp(), bounded query metrics and PostgreSQL prepared statements, plus a strict nightly 10% benchmark gate.Update owned login, refresh, logout, reset-password, change-password and two-factor helper files alongside the framework. Disable PostgreSQL preparation for incompatible poolers.
- 0.10.1
Middleware errors stop the chain; catch-all parameters decode once. Generated password changes revoke sessions, and init offers TOTP, email OTP, or both with enrollment, login and replay protection. Update existing auth templates, model fields and
TWO_FACTOR_KEY; configure SMTP for email OTP. Device-session templates return 501; recovery codes are not implemented.efc db pullkeeps the names of models you already have and never writes a second file for their table —--forcecould overwriteAuditLog.tswith a pulledAuditlogon macOS and Windows. - 0.10.0
Typed MongoDB pipelines and
lookup(),efc db pullfor MongoDB,rateLimitacross workers,efc db studio --write, benchmarks vs Prisma / Drizzle / mongoose — andthread: truetasks work again. - 0.9.6
Security release: update generated auth routes with
efc add … --overwrite(input validation, no token fields in responses, sessions end on suspend). PlusSECURITY.mdand a versioning & LTS policy. - 0.9.5
Connection errors name the fix (missing password, database, role, SSL) instead of echoing the driver; a boot-time warning when
workers × poolexceeds PostgreSQL'smax_connections. - 0.9.4
create-efc-appandefc add authask for the auth strategy: http-only cookie (browsers) or Bearer token (apps). Bearer logins now return the token;'bearer'replaces'localStorage', which still works. - 0.9.3
efc start prodloads.envwhen present; real environment variables still win. - 0.9.2
Zero
npm auditfindings.hashPassword/verifyPassword(scrypt, built into Node) replacebcryptin scaffolded apps. - 0.9.1
defineCollectionfor MongoDB, and auth for your own entities:efc add auth:merchant. - 0.9.0
efc db generatewrites migrations from model changes. Plusefc db pull,efc import-prisma, pool settings and a relations guide. - 0.8.0
efc add: auth, portals, billing and more, copied into your app as source you own. - 0.7.1
The scaffolder generates
defineTablemodels with foreign keys on PostgreSQL. - 0.7.0
Relations for
defineTable: foreign keys, many-to-many,include. Query logging. - 0.5.0
Types inferred from schemas,
defineTable, and an MCP server for the docs. - 0.3
Auth routes and
requireAuth('role'), typed schema fields, default operators and sequences. - 0.2
User and admin portals, RBAC, the route docs dashboard and the mailer in the scaffolder.
- 0.1
First release: file-based routing, clustering, background tasks and JWT auth.
Next: plugins, WebSockets and OpenAPI for 1.0.
Write the part
only you can write.
The router, the cluster, the queue and the data layer are done. Start with one command.