0.12.0OpenTelemetry tracing and production hardening

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.

$ npx create-efc-app my-api
Read the docs
The EFC development dashboard listing a project's routes, such as GET /v1/api/health, with method filters and a typed response example.
The EFC Workspace: a project tree with src/api, src/model and src/tasks, and src/index.ts calling ignite().

Left: the route dashboard every app serves in development. Right: the project that produced it.

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.

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.

src/api/health.ts
src/api/
health.ts
users/
index.ts
[id].ts
docs/
[...slug].ts
[[team]].ts
Routes
GET/health
GET/users
POST/users
GET/users/:id
GET/docs/:slug*
GET/:team?
// 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.

requests 0 · alive 4/4
primary :3000

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.

src/tasks/SendWelcome.ts
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 })
POST /signup
Request received
validate, create user
0 ms
Job queued
enqueue('SendWelcome')
4 ms
202 sent
the user moves on
5 ms
Attempt 1 failed
SMTP timeout, retry in 1 s
2.1 s
Attempt 2 sent
job complete
3.4 s

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.

src/model/Post.ts
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[] }

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-app writes 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.

All of it is in the docs →

Changelog

Recent releases.

  1. 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.

  2. 0.11.0

    Opt-in OpenAPI 3.1 generation from HTTP routes, request validators and metadata, with offline generation and operation overrides. File WS exports 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.

  3. 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.

  4. 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 pull keeps the names of models you already have and never writes a second file for their table — --force could overwrite AuditLog.ts with a pulled Auditlog on macOS and Windows.

  5. 0.10.0

    Typed MongoDB pipelines and lookup(), efc db pull for MongoDB, rateLimit across workers, efc db studio --write, benchmarks vs Prisma / Drizzle / mongoose — and thread: true tasks work again.

  6. 0.9.6

    Security release: update generated auth routes with efc add … --overwrite (input validation, no token fields in responses, sessions end on suspend). Plus SECURITY.md and a versioning & LTS policy.

  7. 0.9.5

    Connection errors name the fix (missing password, database, role, SSL) instead of echoing the driver; a boot-time warning when workers × pool exceeds PostgreSQL's max_connections.

  8. 0.9.4

    create-efc-app and efc add auth ask for the auth strategy: http-only cookie (browsers) or Bearer token (apps). Bearer logins now return the token; 'bearer' replaces 'localStorage', which still works.

  9. 0.9.3

    efc start prod loads .env when present; real environment variables still win.

  10. 0.9.2

    Zero npm audit findings. hashPassword / verifyPassword (scrypt, built into Node) replace bcrypt in scaffolded apps.

  11. 0.9.1

    defineCollection for MongoDB, and auth for your own entities: efc add auth:merchant.

  12. 0.9.0

    efc db generate writes migrations from model changes. Plus efc db pull, efc import-prisma, pool settings and a relations guide.

  13. 0.8.0

    efc add: auth, portals, billing and more, copied into your app as source you own.

  14. 0.7.1

    The scaffolder generates defineTable models with foreign keys on PostgreSQL.

  15. 0.7.0

    Relations for defineTable: foreign keys, many-to-many, include. Query logging.

  16. 0.5.0

    Types inferred from schemas, defineTable, and an MCP server for the docs.

  17. 0.3

    Auth routes and requireAuth('role'), typed schema fields, default operators and sequences.

  18. 0.2

    User and admin portals, RBAC, the route docs dashboard and the mailer in the scaffolder.

  19. 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.

$ npx create-efc-app my-api
Star on GitHub →