npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

express-file-cluster

v0.2.7

Published

Enterprise-grade, zero-boilerplate backend framework with file-based routing and multi-core clustering

Readme

express-file-cluster  ·  efc

File-based routing. Multi-core clustering. Background tasks. Zero boilerplate.

EFC is an opinionated backend framework built on Express. Drop files in src/api/ and they become routes. Every CPU core serves traffic automatically. Heavy work goes to a queue-backed task subsystem so requests stay fast.

Status: v0.2.1 (Beta). The router, clustering, and auth are implemented. The MongoDB adapter and task queue backend are in active development.


Why EFC

Most Express apps grow the same way: a working prototype, then a maze of router.get(...) calls spread across files, a clustering setup copy-pasted from a blog post, and background jobs bolted on as an afterthought. EFC collapses all of that into conventions:

| Problem | EFC's answer | |---|---| | Route registration ceremony | The file tree is the route tree | | Single-threaded Node under load | Auto-detected CPU count → worker processes | | Blocking work on the request path | enqueue() ships it to a queue; respond immediately | | Wiring auth, DB, and middleware by hand | ignite() — one call bootstraps everything |


Quick Start

npx create-efc-app my-api
cd my-api
efc start dev

The interactive scaffolder asks for language, database, auth strategy, and whether you want clustering and background tasks — then writes the boilerplate, generates a .env with a real JWT_SECRET, and runs npm install.


Project Structure

my-api/
├── src/
│   ├── api/                      # Every file here is a route
│   │   ├── health.ts             # GET /health
│   │   ├── users/
│   │   │   ├── index.ts          # GET /users  •  POST /users
│   │   │   └── [id].ts           # GET /users/:id  •  DELETE /users/:id
│   │   └── posts/
│   │       └── [slug]/
│   │           └── comments.ts   # GET /posts/:slug/comments
│   ├── tasks/                    # Background jobs
│   │   ├── SendEmail.ts
│   │   └── ResizeImage.ts
│   ├── models/                   # Engine-agnostic models
│   │   └── User.ts
│   └── index.ts                  # Framework entry point
├── efc.config.ts
├── .env                          # Gitignored — JWT_SECRET auto-filled
└── .env.example

Routing rules:

| File | URL | |---|---| | api/health.ts | /health | | api/users/index.ts | /users | | api/users/[id].ts | /users/:id | | api/posts/[slug]/comments.ts | /posts/:slug/comments |


Route Handlers

Export uppercase HTTP method names. Anything not exported returns 405 Method Not Allowed automatically.

// src/api/users/index.ts
import type { Request, Response } from 'express';
import { User } from '../../models/User';

export const GET = async (req: Request, res: Response) => {
  const users = await User.find();
  res.json(users);
};

export const POST = async (req: Request, res: Response) => {
  const user = await User.create(req.body);
  res.status(201).json({ id: user.id });
};
// src/api/users/[id].ts
import type { Request, Response } from 'express';
import { User } from '../../models/User';
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();
};

Middleware

Three tiers, each with a clear scope:

// 1. Global — applies to every request
// CORS is built-in — configure it via CORS_ORIGINS in .env, not a separate package
ignite({ globalMiddlewares: [rateLimiter()] });

// 2. Route-level — applies to every handler in this file
export const middlewares = [requireAuth];

// 3. Handler-level — applies to one handler via compose()
import { compose } from 'express-file-cluster';

export const POST = compose(
  validateBody(CreateUserSchema),
  async (req, res) => { /* req.body is validated */ },
);

Background Tasks

Tasks run off the request path — respond immediately, let the queue handle the work.

// src/tasks/SendEmail.ts
import { defineTask } from 'express-file-cluster/tasks';

interface Payload { to: string; subject: string; body: string }

export default defineTask<Payload>(async (payload) => {
  await mailer.send(payload);
});
// src/tasks/ResizeImage.ts — CPU-bound: runs in a worker_threads thread
import { defineTask } from 'express-file-cluster/tasks';

export default defineTask<{ key: string; width: number }>(
  { thread: true },
  async ({ key, width }) => {
    const buf = await sharp(await download(key)).resize(width).toBuffer();
    await upload(`${key}@${width}`, buf);
  },
);
// Trigger from a route handler
import { enqueue } from 'express-file-cluster/tasks';

export const POST = async (req, res) => {
  const user = await User.create(req.body);
  await enqueue('SendEmail', { to: user.email, subject: 'Welcome!', body: '...' });
  res.status(202).json({ id: user.id, queued: true });
};

Task options:

| Option | Default | Description | |---|---|---| | thread | false | Run in a worker_threads thread (CPU-bound work) | | retries | 3 | Retry attempts before dead-lettering | | backoff | 'exponential' | Delay strategy between retries | | concurrency | tasks.concurrency | Parallel jobs for this task | | schedule | — | Cron expression for recurring tasks |


Authentication

http-only (recommended for SSR/SSG)

Tokens stored in HttpOnly + Secure + SameSite=Strict cookies.

import { issueToken, revokeToken, requireAuth } from 'express-file-cluster/auth';

// src/api/auth/login.ts
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' });
};

// Protect a route
export const middlewares = [requireAuth];

localStorage (SPA-friendly)

Token returned in body; client attaches Authorization: Bearer <token>.

import { signToken } from 'express-file-cluster/auth';

export const POST = async (req, res) => {
  const token = signToken({ sub: user.id });
  res.json({ token });
};

Bootstrapper

// src/index.ts
import { ignite } from 'express-file-cluster';
import path from 'path';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

ignite({
  port: Number(process.env.PORT) || 3000,
  apiDir: path.join(__dirname, 'api'),
  tasksDir: path.join(__dirname, 'tasks'),

  database: 'mongodb',
  databaseUrl: process.env.DATABASE_URL,

  authStrategy: 'http-only',
  jwtSecret: process.env.JWT_SECRET,

  cluster: true,          // false → single process (auto-disabled in dev)
  workers: 4,             // defaults to os.cpus().length

  tasks: {
    backend: 'bullmq',
    redisUrl: process.env.REDIS_URL,
    concurrency: 5,
  },

  globalMiddlewares: [],
  onWorkerReady: (id) => console.log(`Worker ${id} ready`),
  onWorkerCrash: (id, code) => console.error(`Worker ${id} crashed (${code})`),
});

ignite() options

| Option | Type | Default | Description | |---|---|---|---| | port | number | 3000 | HTTP listen port | | apiDir | string | — | Path to route modules | | tasksDir | string | — | Path to task modules | | database | 'mongodb' \| 'postgresql' | — | Database engine | | databaseUrl | string | DATABASE_URL | Connection string | | authStrategy | 'http-only' \| 'localStorage' | — | Token delivery | | jwtSecret | string | JWT_SECRET | JWT signing secret | | cluster | boolean | true | Enable multi-core clustering | | workers | number | os.cpus().length | Worker count override | | tasks | TaskConfig \| false | false | Background task runtime | | cors | boolean \| CorsConfig | true | CORS — origins driven by CORS_ORIGINS env var | | globalMiddlewares | RequestHandler[] | [] | Applied to every route | | onWorkerReady | (id) => void | — | Called when a worker boots | | onWorkerCrash | (id, code) => void | — | Called before respawn | | onError | ErrorRequestHandler | built-in | Override global error handler |


CLI Reference

# Development
efc start dev         # Hot-reload single process, source maps, pretty logs

# Production
efc build prod        # Type-check + compile to dist/ (tsup, dual CJS/ESM)
efc start prod        # Run dist/ with clustering enabled

# Tests
efc run tests         # Vitest  (--watch, --coverage passthrough)

# Code generation
efc generate route users/[id]       # → src/api/users/[id].ts
efc generate task ProcessPayment    # → src/tasks/ProcessPayment.ts
efc generate middleware authorize   # → src/middlewares/authorize.ts

# Diagnostics
efc routes            # Print resolved route table (path → file → methods)
efc tasks             # List registered background tasks
efc doctor            # Validate config, env vars, DB connectivity

Clustering Architecture

              Master Process
         ┌────────────────────┐
         │  fork × N workers  │
         │  respawn on crash   │
         └──┬──────┬──────┬───┘
            │      │      │
       Worker 1  Worker 2  Worker N
       Pre-Flight lifecycle per worker:
         1. Connect DB
         2. Configure auth
         3. Scan apiDir → route map
         4. Register tasks
         5. Mount routes on Express
         6. Listen (OS round-robins connections)

CPU-bound tasks fan out further into worker_threads — the request loop stays unblocked at every layer.


Error Handling

import { HttpError } from 'express-file-cluster';

// Throw from any handler — caught and formatted automatically
export const GET = async (req, res) => {
  const user = await User.findById(req.params.id);
  if (!user) throw new HttpError(404, 'User not found');
  res.json(user);
};

// Override the global handler
ignite({
  onError: (err, req, res, next) => {
    logger.error(err);
    res.status(err.statusCode ?? 500).json({ error: err.message });
  },
});

Environment Variables

create-efc-app generates .env (gitignored, JWT_SECRET pre-filled) and .env.example (committed, documented).

| Variable | Required | Description | |---|---|---| | PORT | No (default 3000) | HTTP listen port | | NODE_ENV | No | development \| production \| test | | DATABASE_URL | Yes | MongoDB or PostgreSQL connection string | | JWT_SECRET | Yes | JWT signing key — auto-generated by scaffolder | | JWT_EXPIRES_IN | No (default 7d) | Token lifetime | | COOKIE_DOMAIN | No | Cookie domain for http-only auth | | REDIS_URL | If using BullMQ | Redis connection for the task queue | | CORS_ORIGINS | No | Comma-separated allowed origins — e.g. http://localhost:3000,https://myapp.com |

License

MIT © 2026 EFC Contributors