Rhea.js 1.0: a security-first backend framework for Node.js and Express 5
I just released Rhea.js 1.0, an open-source backend framework for Node.js. It sits on top of Express 5 and adds the parts every API ends up needing: a project layout, security defaults, validation, logging, authenticatio
I just released Rhea.js 1.0, an open-source backend framework for Node.js. It sits on top of Express 5 and adds the parts every API ends up needing: a project layout, security defaults, validation, logging, authentication and a CLI that generates code for you.
npx create-rhea my-api
This post explains what it is, why I built it, what you get, and, just as important, what it does not do yet.
The problem
Every Express project I started began the same way. Install Express. Add Helmet. Add CORS. Remember the body size limit. Add a rate limiter. Add a logger and then notice it prints passwords. Write an error handler. Decide on a folder layout. Copy the auth code from the last project.
Two things bothered me about that routine:
-
Security is opt-in. If you forget a step, nothing warns you. A fresh
express()app has no security headers, no limits, and no opinion. - Every team invents its own structure. Nobody can open a new project and know where the validation lives.
Rhea.js is my answer: keep Express as the HTTP engine, and put the architecture and the safe defaults around it.
What Rhea.js is
- A runtime package,
@rheajs/core, with acreateApp()function. - An auth package,
@rheajs/auth(passwords, JWT, refresh tokens, guards). - A CLI,
@rheajs/cli, and the scaffoldercreate-rhea. - Express 5 underneath. Your routes are normal Express routes. If you know Express, you already know most of Rhea.js.
It is MIT licensed, written in TypeScript, and works with TypeScript or JavaScript, ES Modules or CommonJS.
Get started in one minute
npx create-rhea my-api
cd my-api
npm run dev
The scaffolder asks four things: TypeScript or JavaScript, ES Modules or CommonJS, a database (MongoDB, MySQL or none) and, if you picked a database, whether to add authentication. Then it installs dependencies, runs git init, makes the first commit, and, if you have the GitHub CLI, can create the GitHub repository for you.
Open http://localhost:5000/api/rhea and you have a running API. Add a resource:
npx rhea generate module users
That creates the controller, service, repository, routes, Zod schema, types and an integration test, and registers the module.
What you get by default
Everything below is on from the first request, with no setup:
- Request IDs on every request and every error response, so you can find a log line from a bug report.
-
Helmet security headers, plus a
Permissions-Policyheader. - CORS denied by default. You list the origins you trust. A wildcard is refused in production.
- Rate limiting, with an option to share counters through Redis when you run more than one instance.
-
Body size limit, request timeout and JSON-only bodies (a form-encoded body gets a clean
415). -
Prototype-pollution guard, and an optional guard against MongoDB operator keys such as
{"$ne": null}in a login body (on in generated MongoDB projects). -
One error shape. Throw
NotFoundErrororConflictErrorfrom anywhere and the client always gets the same JSON. Stack traces never leave the server in production. - Structured logs with pino: JSON in production, readable in development, and passwords, tokens and cookies are redacted. In development you also get a short morgan line per request.
- Validation with Zod for body, query and params, and environment validation that stops startup when your config is wrong.
- Graceful shutdown and process error handlers.
A route looks like this:
import { Router, sendSuccess, validate, z } from "@rheajs/core";
const createUser = z.object({ name: z.string().min(1).max(100) });
export const users = Router();
users.post("/", validate(createUser), (req, res) =>
sendSuccess(res, { name: req.body.name }, "Created", 201),
);
Invalid input returns 422 with a list of exactly which field failed, and the keys Zod strips never reach your handler.
Authentication that is not a tutorial snippet
Pick a database and say yes to authentication, and the generated project gets a complete flow:
-
POST /api/auth/register,login,refresh,logout,logout-allandGET /api/auth/me. - Short-lived access token (a JWT, 15 minutes) and a rotating refresh token. The refresh token is random, stored only as a SHA-256 hash, and replaced every time you use it.
- Reuse detection. If an old refresh token is used again (a sign of theft), the whole session family is revoked. A short grace window handles honest double requests such as a flaky network.
-
Cookie mode for browser apps. Set
AUTH_REFRESH_COOKIE=trueand the refresh token lives in anHttpOnly,SameSite=Strictcookie, with anOrigincheck against CSRF. No token inlocalStorage. - Account lockout after five wrong passwords, and a common-password blocklist.
- scrypt password hashing with OWASP-level parameters.
Protecting your own route is one line:
import { authenticate, requireRole } from "@rheajs/auth";
router.delete("/:id", authenticate(jwt), requireRole("admin"), handler);
I tested this flow against real MongoDB 7 and MySQL 8.4 over HTTP, including ten parallel refreshes with the same token (exactly one wins), and with a Next.js todo app in a real browser.
The CLI
| Command | What it does |
|---|---|
rhea create |
scaffold a project |
rhea dev |
run with auto restart and graceful shutdown |
rhea generate module users |
controller, service, repository, routes, schema, test |
rhea doctor |
check your setup |
rhea security |
static scan for common config mistakes (--audit adds npm audit) |
rhea docker |
write a multi-stage, non-root Dockerfile |
rhea build / rhea start
|
production build and run |
Why not NestJS, Fastify or plain Express?
Honest answer: they are all good, and the right choice depends on you.
- Plain Express is the fastest to start and the slowest to make safe. Rhea.js is Express plus the checklist.
- NestJS is a large, decorator-based framework with dependency injection. Rhea.js is much smaller and has no decorators. It is plain functions and modules.
- Fastify is faster and has its own ecosystem. If raw throughput is your top need, use Fastify.
Choose Rhea.js if you want Express and a safe starting point, with a layout your whole team can predict.
The honest part
- Performance. On a trivial route Rhea.js reaches about 60% of the throughput of bare Express on my machine, because it does real work on every request (IDs, logging, headers, limits, checks). The numbers and the method are in BENCHMARKS.md. If you need raw speed and none of the checks, use bare Express.
- Security review. Rhea.js is used in production by me. It has not had an independent security review. I wrote a self-audit that lists what was tested, what was fixed, and what is still open. If you can review it, I would love that.
- Not included yet: server-side sessions, OAuth/OIDC, MFA, password reset, and an ORM. The rate limit is per process unless you pass a shared store.
- Stability. From 1.0 it follows semantic versioning, and the docs say exactly what the public API is.
Try it and tell me what breaks
npx create-rhea my-api
- Docs: https://rhea.devcodehub.cloud
- GitHub: https://github.com/dev-shamsali/Rhea.js-framework
- npm: https://www.npmjs.com/org/rheajs
If you try it, I would like to hear what was confusing or broken. Bug reports, questions and pull requests are all welcome. If you find a vulnerability, please follow SECURITY.md and report it privately.
Thanks for reading.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.