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

bro-framework

v3.0.6

Published

The No-BS Backend Framework for Node.js

Readme


Skip the boilerplate. bro.js gives you file-based routing, automatic Zod validation, JWT auth, and WebSockets right out of the box. Just write your business logic, return an object, and let the framework handle the rest.


Table of Contents


Getting Started

Bootstrapping a new bro.js project is incredibly simple. We recommend using our official scaffolding tool to set everything up instantly (with your choice of JavaScript or TypeScript):

npx create-bro-framework@latest my-api
cd my-api
npm run dev

That's it! Your zero-boilerplate backend is now running with hot-reloading enabled.

Configuration

Configuration lives in bro.config.js:

import { defineConfig } from 'bro-framework';

export default defineConfig({
  server: { port: 5000, cors: true, helmet: true },
  auth: {
    jwtSecret: process.env.JWT_SECRET,
    expiresIn: '7d',
    apiKey: process.env.API_KEY
  },
  locale: { directory: './locale', defaultLocale: 'en' },
  rateLimit: { windowMs: 15 * 60 * 1000, max: 100 },
  docs: process.env.NODE_ENV !== 'production',
  redisUrl: process.env.REDIS_URL,
  db: async () => {
    // Initialize database connection here
  },
  sockets: async (io, db) => {
    // Setup Socket.IO event listeners here
  },
  onShutdown: async (db) => {
    // Close application-owned database resources here.
  }
});

cors: false disables HTTP CORS middleware. Helmet is enabled by default and can be disabled with helmet: false. Redis is optional; when configured it powers distributed rate limiting, route caching, and Socket.IO scaling. In NODE_ENV=test, bro.js can inject ioredis-mock; install it in the consuming project's development dependencies.

Production Security Note: When deploying behind a reverse proxy (Nginx, AWS ALB, Vercel, Render), ensure your load balancer properly sets X-Forwarded-For. Rate limiting and trusted IP functionality relies on this proxy configuration.


The Core Experience

In bro.js, everything you need is handed to you instantly. No setup, no middleware wrangling, no manual req/res handling. You define your route, set your validation, and return an object.

routes/posts/[id].post.js:

import { defineRoute, z } from 'bro-framework';

export default defineRoute({
  auth: true,
  params: z.object({
    id: z.string().uuid()
  }),
  body: z.object({
    title: z.string().min(5),
    content: z.string()
  }),
  query: z.object({
    draft: z.coerce.boolean().default(false)
  }),
  response: z.object({
    success: z.boolean(),
    updated: z.number()
  }),
  handler: async ({ body, params, user, db, io }) => {
    // 1. Data is already validated. user is already authenticated.
    
    // 2. Perform database operation using the injected Mongoose context
    const post = await db.collection('posts').updateOne(
      { _id: params.id },
      { $set: { ...body, authorId: user.id } }
    );
    
    // 3. Broadcast to all clients instantly using injected Socket.io
    io.emit('post_updated', { postId: params.id, title: body.title });
    
    // 4. Return an object. bro.js handles the 200 JSON response.
    return {
      success: true,
      updated: post.modifiedCount
    };
  }
});

Validation schemas are flat and must be declared directly as body, params, and query. The deprecated nested schema: { body, params, query } form is rejected during route loading. The optional response schema documents the successful JSON response in OpenAPI; it does not runtime-validate handler output.


Deep-Dive Features

File-Based Routing

Create a .js file in the routes/ directory, and it automatically becomes an endpoint. We use Next.js-style bracket syntax for dynamic parameters. A file named routes/users/[id].get.js translates natively to a GET /users/:id Express route under the hood.

Bouncer-Grade Validation

Powered by Zod. Attach a schema to body, query, or params directly in your route definition. If the client sends malformed data, bro.js automatically rejects the request with a structured 400 Bad Request JSON payload before your handler ever executes. You never have to manually validate inputs again. You can also define a response schema to strongly type your OpenAPI documentation (strictly opt-in; arbitrary 200s work out of the box).

Zero-Config Auth (JWTs, RBAC, API Keys)

Add auth: true to your route config. bro.js will intercept the request, extract the Authorization: Bearer <token> header, verify the signature using your jwtSecret, and inject the decoded payload directly into ctx.user. You can also use Role-Based Access Control by passing an array of roles (e.g. auth: ['admin']) or enforce service-to-service communication with auth: 'api-key'. API keys are read from auth.apiKey or API_KEY and support zero-downtime rotation with an array:

auth: {
  apiKey: ['current-key', 'next-key']
}

Clients send the selected key in the x-api-key header. API-key routes are represented as apiKeyAuth operations in OpenAPI.

Context Injection

Stop importing singleton database connections and socket instances into every file. Define your db and sockets setup once in bro.config.js. bro.js orchestrates the initialization and injects both instances directly into the ctx object for every request handler.

Redis Caching and Lifecycle

Set redisUrl to enable distributed rate limiting, route caching, and Socket.IO pub/sub scaling. Add cache: 60 to a route to cache its JSON response for 60 seconds. Cache keys include the request URL, resolved locale, and authorization/API-key identity; do not cache responses with dimensions that are not represented in the key.

The programmatic createServer() API returns shutdown(). It stops scheduled tasks, closes Socket.IO, closes Redis clients, runs the optional onShutdown(db) hook, and closes the HTTP server. The CLI calls it automatically on SIGINT and SIGTERM.

Zero-YAML Live Documentation

If you've ever hand-written OpenAPI YAML, you know the pain. bro.js parses your Zod schemas and automatically serves a stunning, interactive Scalar API playground at /docs. It's highly secure: by default, these internal docs are disabled in production mode.

The Frontend SDK Generator

Tired of writing frontend fetch wrappers? Run bro sdk. The CLI parses your backend routes and compiles a JavaScript bro-sdk.js file. It includes token and locale headers, query serialization, URL-encoded dynamic parameters, and deep tree traversal (e.g., api.users.id("123").get()). Configure it with setBaseURL(), setTokenKey(), and setLocale().

Background Task Scheduler

Don't spin up a separate worker server. Drop a JavaScript file anywhere in the tasks/ folder, export a cron string (e.g., "0 0 * * *"), and an async handler. bro.js natively schedules it as a background worker with full access to your injected database and WebSocket contexts.

Zero-Boilerplate File Uploads

Add upload: true to a route. bro.js uses Multer to parse multipart/form-data and inject files into the context. Use single, array, or fields for explicit field handling:

export default defineRoute({
  upload: {
    single: 'avatar',
    limits: { fileSize: 5 * 1024 * 1024 }
  },
  handler: ({ file }) => ({ name: file?.originalname })
});

ctx.file is used by single(). ctx.files is an array for array() or a field-to-array object for fields(). Defaults include limits for file size, file count, fields, parts, and field size. Use storage for production disk/object-storage integration instead of retaining large buffers in memory.

File-Based Locale

Create a locale/ folder with one translation file per locale, such as locale/en.js and locale/fr.js. Export a plain object from each file, then use t() in any route:

// locale/fr.js
export default { welcome: 'Bienvenue, {name} !' };

// routes/welcome.get.js
export default defineRoute({
  handler: async ({ t }) => ({ message: t('welcome', { name: 'Sam' }) })
});

The locale is negotiated dynamically using RFC 9110 Accept-Language headers, supporting full region fallback and custom defaults, and the generated SDK can securely set it via setLocale('fr').

Route Reference

| Option | Type | Purpose | | :--- | :--- | :--- | | auth | boolean \| string[] \| 'api-key' | JWT, role, or API-key protection. | | body | Zod schema | Validates JSON request bodies. | | params | Zod schema | Validates URL parameters. | | query | Zod schema | Validates query-string values. | | response | Zod schema | Documents successful JSON output in OpenAPI. | | cache | number | Redis response-cache duration in seconds. | | rateLimit | { windowMs, max } | Route-specific request limiting. | | upload | boolean or options | Enables Multer parsing and limits. | | summary | string | OpenAPI operation summary. |

Dynamic route files use bracket parameters such as routes/users/[id].get.js. Nested dynamic directories are supported. HTTP method suffixes are get, post, put, delete, patch, options, and head.

Context Reference

Handlers receive:

| Property | Description | | :--- | :--- | | body | Validated body data. | | params | Validated path parameters. | | query | Validated query data. | | user | Decoded JWT payload when JWT auth is used. | | db | Value returned by config.db. | | io | Socket.IO server instance. | | redis | Redis client when Redis is enabled or test mode is active. | | file | Single uploaded file from upload.single(). | | files | Array or field map from array()/fields(). | | locale | Resolved request locale. | | t | Translation function, t(key, values). | | jwt | Configured JWT signing helper. | | env | Parsed environment data when configured, otherwise process.env. |

Testing

For Redis-backed integration tests without an external Redis server, install ioredis-mock in the consuming project and run with NODE_ENV=test. bro.js injects a mock Redis client and exercises cache, rate-limit, Socket.IO adapter, and shutdown paths.

Supertest + Vitest Recipe

You can programmatically bootstrap bro.js using createServer for blazing fast integration tests. Here's a complete vitest recipe:

import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import request from 'supertest';
import path from 'path';
import { createServer } from 'bro-framework';
import config from '../bro.config.js';

describe('API Tests', () => {
  let app, shutdown;

  beforeAll(async () => {
    // 1. Initialize the server programmatically
    const instance = await createServer(config, path.resolve('./routes'), null);
    app = instance.app;
    shutdown = instance.shutdown;
  });

  afterAll(async () => {
    // 2. Cleanly teardown tasks, redis, and sockets
    if (shutdown) await shutdown();
  });

  it('should return a 200 from the healthcheck', async () => {
    const res = await request(app).get('/health/live');
    expect(res.status).toBe(200);
    expect(res.body.status).toBe('ok');
  });
});

Architecture & Request Lifecycle

       [ Incoming HTTP Request ]
                  │
                  ▼
         ( Express Engine )
                  │
                  ▼
     [ CORS / JSON Pre-flight ]
                  │
                  ▼
          ( Dev Logger )
                  │
                  ▼
      [ Auth Guard (JWT Check) ] ──(Fail)──> 401 Unauthorized
                  │
                  ▼
     [ Zod Bouncer Validation ] ───(Fail)──> 400 Bad Request
                  │
                  ▼
         ( Route Handler )
      ╭─────────────────────╮
      │ Injects:            │
      │ - ctx.body / params │
      │ - ctx.user          │
      │ - ctx.db            │
      │ - ctx.io            │
      │ - ctx.files         │
      ╰─────────────────────╯
                  │
                  ▼
      [ Auto JSON Formatter ] ─────(Fail)──> 500 Internal Error
                  │
                  ▼
      [ Client JSON Response ]

Tech Stack Breakdown

| Layer | Technology | Purpose | | :--- | :--- | :--- | | Engine | Node.js (Express) | High-performance, battle-tested HTTP abstraction layer. | | Validation | Zod | Bouncer-grade, strictly typed schema validation for payloads. | | Authentication | jsonwebtoken | Stateless, scalable security for protecting endpoints. | | Realtime | Socket.io | Bi-directional, event-driven WebSocket communication. | | API Reference | Scalar | Auto-generated, interactive Swagger/OpenAPI documentation. | | Task Scheduler | node-cron | Reliable internal background task orchestration. | | File Parsing | multer | Zero-boilerplate multipart/form-data file extraction. | | Caching & Scaling | Redis | Optional zero-config route caching, distributed rate-limiting, and WebSocket scaling. | | Security | Helmet | Auto-configured industry-standard HTTP security headers. |


CLI Reference

| Command | Description | | :--- | :--- | | bro dev | Development server featuring instant boot, visual CLI banner, and chokidar-powered hot module remapping. | | bro start   | Production runner locked down for security. Features Graceful Shutdown APIs (with onShutdown DB teardown), suppressed internal logs, and isolated API docs. | | bro init | Automated workspace scaffolder. Generates configuration files and forcefully ensures your package.json respects "type": "module". | | bro sdk | Route parser and browser client compiler. Generates your typed bro-sdk.js frontend SDK in one hit. |

Next.js App Router Integration

You can natively use bro.js syntax, Zod validation, and Authentication inside your Next.js API routes (app/api/.../route.ts)!

First, create your factory instance (e.g., lib/bro.ts):

import { createBro } from 'bro-framework/next';

export const { defineRoute, z } = createBro({
  auth: { apiKey: process.env.API_KEY }
});

Then use it seamlessly in your route files:

import { defineRoute, z } from '@/lib/bro';

export const POST = defineRoute({
  body: z.object({ name: z.string() }),
  handler: async ({ body }) => {
    return { success: true, hello: body.name };
  }
});

Capabilities & Limitations: Because Next.js API routes are "Serverless" (meaning they sleep when not actively processing a request), features that require a constantly running server such as WebSockets, Background Tasks (Cron), Rate Limiting, and Auto-generated Docs are strictly limited to the standalone bro.js framework and are not available in the Next.js adapter.


Compatibility Table

| bro.js Version | Node.js | Next.js App Router | Express | Zod | | :------------- | :-------- | :----------------- | :------ | :------ | | >= 2.0.0 | >= 18.x | >= 13.4.x | 4.x | 3.x |


Author & License

  • Author: Yass1n (@medyass1ne)
  • License: MIT