The backend framework you own, line by line.
CalmAPI copies a small, readable framework into your project. Declare a resource once and get routes, validation, access rules and an OpenAPI document. Then prove the rules hold with a built-in security audit, and watch your coding agent work in devtools.
- Runtime dependencies
- express, mongoose
- Framework source in your repo
- 5,540 lines
- CLI dependencies
- none
- Framework tests, on real MongoDB
- 204
- Tools for coding agents (MCP)
- 18


/_calm, while Claude Code adds a route. Its edit crashes the server, it reads the crash with calm_activity, fixes the line and tests again. Your own edit sits in the same feed. Violet marks the agent.Declare a resource once. The rest follows.
Fields and access rules live in one file. CalmAPI turns them into the Mongoose schema, input validation, output filtering, query rules, five routes and the API reference. Business logic goes in a service with hooks, custom endpoints in a routes file. Both are optional.
import { resource, f } from '#calm';export default resource('post', { fields: { title: f.string().required().max(200).searchable().sortable(), slug: f.string().readOnly().unique().filterable(), body: f.text().required().max(50_000), status: f.enum(['draft', 'published']).default('draft').filterable(), tags: f.array(f.string().max(30)).max(10).default([]).filterable(), author: f.ref('user').owner(), // set from the session; powers the 'owner' policy publishedAt: f.date().readOnly().sortable(), }, policy: { list: 'public', // drafts are filtered out in post.service.js scope() read: 'public', create: 'authenticated', update: 'owner | admin', delete: 'owner | admin', },});from that one file
- Five routes, each with the policy you wrote.
calm routeslists them with their source file. - Validation that rejects unknown, read-only and hidden fields with a 422, before your code runs.
- Filtering, sorting and search on the fields you mark, and a 400 for anything else.
- Responses filtered per caller. A field with
visibleTo('owner | admin')never reaches anyone else. - Owner scoping. With an
ownerpolicy, lists only return the caller's own records. - OpenAPI 3.1, generated from the same definition, so the docs cannot drift.
METHOD PATH POLICY FROM GET /api/posts/featured public post.routes.js POST /api/posts/:id/publish owner | admin post.routes.js GET /api/posts public post.resource.js GET /api/posts/:id public post.resource.js POST /api/posts authenticated post.resource.js PATCH /api/posts/:id owner | admin post.resource.js DELETE /api/posts/:id owner | admin post.resource.js
Access rules you can prove.
Every route says who may call it and every field says who may see it. Rules only matter if they hold, so calm audit boots your app on a throwaway database, signs in as every role and calls every route. On the starter app that is 54 checks in under two seconds, and --ci fails the build when one breaks.
a list override an agent might write to add sorting
import { ResourceService } from '#calm';export default class NoteService extends ResourceService { // Newest first, with a larger page. async list({ filter = {}, skip = 0, limit = 50 } = {}) { const [items, total] = await Promise.all([ this.model.find(filter).sort({ _id: -1 }) .skip(skip).limit(limit).lean(), this.model.countDocuments(filter), ]); return { items, total }; }}Notes are owner-only, but lines 7 and 8 query the model directly. Every unit test for the service still passes. The fix is one line: return super.list(...) and let the framework apply the rule.
what the audit says
note /notes action Anonymous Signed-in user Owner Admin list ✔ deny ✖ own (200) ✖ own (200) ✔ allow read ✔ deny ✔ deny ✔ allow ✔ allow create ✔ deny ✔ allow ✔ allow ✔ allow update ✔ deny ✔ deny ✔ allow ✔ allow delete ✔ deny ✔ deny ✔ allow ✔ allow ✖ list as Signed-in user: returned 2 record(s) owned by someone else ✖ list as Owner: returned 1 record(s) owned by someone else Probes ✔ mass assignment: note.owner rejected with 422 ✔ operator injection: note x[$ne]=1 -> 400 ✔ forged session token -> 401 ✔ login does not reveal accounts 401 / 401 ... 2 failed · 75 passed · 2 skipped (custom scope or policy) · 79 checks
calm/security/exposure.js
The audit checks against a matrix computed from your code.
Who can list, read, change and delete each resource, and which fields each caller sees. Devtools shows it, calm doctor warns when personal data is public, and coding agents read it through the calm_security tool. This is the matrix of a new project.
| action | anonymous | signed in | owner | admin |
|---|---|---|---|---|
| post /api/posts | ||||
| list | allow | allow | allow | allow |
| read | allow | allow | allow | allow |
| create | deny | allow | allow | allow |
| update | deny | deny | allow | allow |
| delete | deny | deny | allow | allow |
| user /api/users | ||||
| list | deny | deny | deny | allow |
| read | deny | deny | allow | allow |
| update | deny | deny | allow | allow |
| read emailpii | no | no | yes | yes |
| read roles | no | no | yes | yes |
| read password | never | never | never | never |
access control
- A policy on every route, listed by calm routes and checked by calm doctor.
- Field rules for reading and writing, per role and for the owner.
- 401, 403 or 404 chosen so a caller who cannot read a record never learns it exists.
- No mass assignment: unknown, read-only, hidden and owner fields are rejected with 422.
accounts
- Opaque sessions stored as SHA-256 hashes. List them, end one, end all.
- scrypt passwords from node:crypto. Common passwords and ones with your name or email are refused.
- Lockout: 5 failed logins in 15 minutes locks that account for 15 minutes.
- Two-factor login with the totp block: RFC 6238, replay protection, hashed recovery codes.
data and input
- .encrypted() fields: AES-256-GCM with key ids, rotated with calm keygen --write.
- Query allowlists: x[$ne]=1 is a 400, and page size is capped.
- JSON guard: 1 MB body, depth 20, 10,000 keys.
- Production by default: devtools can never run there, security headers on, CORS closed.


Your agent writes the code. You see every step.
Coding agents connect to calm mcp and get 18 tools for this project. Everything they do lands in one activity log that devtools streams live: tool calls, file diffs, restarts, crashes and the requests they send. The agent reads the same log, so it notices when an edit breaks the server, yours or its own.
the agent
Claude Code, Cursor, Codex
Reads AGENTS.md for the project's rules. Scaffolds with calm_make_resource, calls the API as any user with calm_http, then runs calm_test and calm_audit.
calm dev + calm mcp
One activity log
Every tool call is logged with its arguments, result and duration. Every file change is diffed and credited to the agent if it acted in the last 20 seconds. Its API calls carry an X-Calm-Agent header.
you
Devtools at /_calm
Activity shows each step with its diff, live. Requests marks the agent's calls with their queries. When it says it is done, run the audit yourself in one click.


calm_http carry a violet marker in the list and in the header, next to the user it acted as. Each has its headers, body, queries and logs, like your own requests.understand
calm_projectresources, blocks, server statecalm_routesevery route and its policycalm_resourceone full definitioncalm_openapithe API documentcalm_blockswhat can be added
change
calm_make_resourcescaffold from field specscalm_add_blockwith a dry run first
run and observe
calm_httpcall the API as anyonecalm_recent_requestswhat just happenedcalm_requestqueries, logs, stackcalm_logsserver log linescalm_mailsemails the app sentcalm_pushespush notifications sentcalm_activitydiffs, restarts, crashes
verify
calm_testpass and fail per testcalm_doctorfindings to fixcalm_auditthe authorization checkcalm_securitymatrix and events
Connect your agent in one line.
Any MCP client works the same way. New projects include AGENTS.md and CLAUDE.md, so the agent knows the conventions before it writes a line. Working with agents
$ claude mcp add calm -- npx calm mcpDevtools that ship inside your app.
Run npm run dev and open /_calm. No extra service, no browser extension. It only runs in development, only answers on localhost, and can never be turned on in production. Every panel explained


Every line is yours. Upgrades still work.
The framework is copied into calm/: about 5,500 lines of commented JavaScript on Express 5 and Mongoose. Read it, step through it, change it. calm.lock remembers what you started from, so calm upgrade replaces the files you never touched and three-way merges the ones you did.
calm/ the framework, yours to read and edit app.js config.js testing.js openapi.js auth/ resource/ http/ security/ devtools/ app/ resources/post/ post.resource.js, .service.js, .routes.js resources/user/ blocks/ blocks you add, as source routes.js test/ node:test with createTestApp AGENTS.md CLAUDE.md rules for coding agents calm.config.js calm.lock .env Dockerfile
calm upgrade 2.0.0-alpha.0 → 2.0.0-alpha.1 update calm/http/router.js update calm/resource/query.js merge calm/auth/index.js conflict calm/config.js add calm/security/report.js Apply? yes ✔ calm/ upgraded to 2.0.0-alpha.1 ! Resolve conflict markers in: calm/config.js
Untouched files are replaced, edited files are merged with your changes kept, and conflicts get the usual markers. The test suite runs a real upgrade over an edited project for each case. Upgrading
Add features as source code.
A block is copied into app/blocks/ with its dependencies and environment variables, then it is yours to edit like everything else. Your own blocks take one command to start and one to share, and anyone can install them from GitHub after a plain-language review of what the code does. Browse blocks · Build your own
- notificationsno deps
- One call for the in-app inbox, push and email, following each user's preferences and quiet hours.
- webhooksno deps
- Signed webhooks for resource events, retried from a MongoDB queue, with a delivery log and SSRF protection.
- api-keysno deps
- API keys with scopes for scripts and integrations: shown once, hashed at rest, with expiry and revoke.
- magic-linkno deps
- Passwordless sign-in by email. Links work once, expire, and only in the browser that asked.
- feature-flagsno deps
- Kill switches, gradual rollouts, per user and per role targeting, variants and route gates.
- privacyno deps
- Users download everything they own and delete their account: delete, anonymize or keep per resource.
- idempotencyno deps
- The Idempotency-Key header: a retried payment or order runs once and replays the stored response.
- totpno deps
- Authenticator-app codes on login: setup by QR link, hashed single-use recovery codes, replay protection.
- jobsno deps
- Background jobs and cron on MongoDB, without Redis: retries with backoff, schedules, a devtools panel.
- mcp-serverno deps
- Your resources as MCP tools for AI agents, behind the same policies and validation as the REST API.
- realtime-sseno deps
- Live changes over Server-Sent Events, filtered per subscriber by the same rules as the API.
- audit-logno deps
- Who changed what and when, with field-level diffs. Private fields are never recorded.
- soft-deleteno deps
- DELETE marks records deleted, queries hide them, admins list and restore them.
- storage-s3@aws-sdk
- Direct uploads to S3, R2 or MinIO with presigned URLs, type and size allowlists.
- storage-localmulter
- Uploads to disk with type and size allowlists, magic-byte checks and safe downloads.
- api-docsno deps
- An interactive API reference at /docs, built from the generated OpenAPI document.
- rate-limit-mongono deps
- Rate limit counters in MongoDB, so limits hold across every instance.
- mail-resendno deps
- Send email through the Resend HTTP API with plain fetch.
- mail-smtpnodemailer
- Send email through any SMTP server.
- push-expono deps
- Push notifications to Expo and React Native apps, with receipts. Dead tokens are removed for you.
- push-fcmno deps
- Push notifications to Android, iOS and the web through Firebase Cloud Messaging, signed with node:crypto.
Start in a minute.
Create a project
Choose the api template for sign-up, users and posts, or minimal for an empty app.
$npm create calmapi@latest my-apiRun it
The API answers on localhost:5001/api and devtools on localhost:5001/_calm.
$cd my-api && npm run devHand it to your agent
Ask for a resource, then watch the Activity panel while it works.
$claude mcp add calm -- npx calm mcpCheck the result
Tests, then the authorization audit. Both run against a throwaway database.
$npm test && npx calm audit
the calm command
- calm create
- New project from the api or minimal template.
- calm dev
- Watch mode, devtools and the activity log.
- calm make resource
- Scaffold a resource from field specs.
- calm add
- Add blocks, from the registry or GitHub, with a safety review.
- calm make block
- Start your own block, then check and pack it to share.
- calm seed
- Sample records that look real, built from your fields.
- calm routes
- Every route, its policy and its source file.
- calm audit
- Every route as every role, on a throwaway database.
- calm doctor
- Configuration, exposure, secrets and advisories.
- calm openapi
- Write the OpenAPI 3.1 document.
- calm keygen
- Keys for encrypted fields, with rotation.
- calm admin
- Give a user the admin role, or take it away.
- calm upgrade
- Merge a new framework release into calm/.
- calm mcp
- MCP server for coding agents.
Coming from CalmAPI 1.x? Run npx calmapi@next doctor in your 1.x project first, then follow the migration guide. The 1.x documentation stays online at docs.calmapi.dev/v1.
Read every line. Prove every rule.
npm create calmapi@latest