bro-framework
v3.0.6
Published
The No-BS Backend Framework for Node.js
Maintainers
Keywords
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
- Configuration
- The Core Experience
- Deep-Dive Features
- Route Reference
- Context Reference
- Architecture & Request Lifecycle
- Tech Stack Breakdown
- CLI Reference
- Next.js App Router Integration
- Author & License
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 devThat'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.jsframework 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
