Boltforge.
A CLI that scaffolds a production-ready Node.js / Express / MongoDB backend — then keeps generating fully-wired features, one command at a time.
What is Boltforge? #
Boltforge is a code generator, not a framework. It writes a real Express + MongoDB project to disk — files you own, read, and edit like any hand-written project — and then keeps adding complete features to it on demand.
There is no runtime dependency on Boltforge. Once a project is generated you can uninstall the CLI and the project still runs, tests, and deploys normally. What Boltforge gives you is the first 80% of a backend that you'd otherwise write by hand every time: authentication, role/permission authorization, an admin area, error handling, validation, logging, security middleware, and a test harness — all wired together and consistent.
Generic engine
Every generator is discovered from disk at startup. Adding a new one is a new folder, not a CLI edit.
Nothing unused
Pick OTP and no password code is generated at all. Skip sockets and no socket server exists anywhere.
Never rewrites your files
Routes and permissions are discovered at startup. No generator edits a shared file to "register" anything.
All-or-nothing writes
Files are staged in a temp directory and moved in one atomic step. A failed generation leaves nothing behind.
Why Boltforge? #
Most starter templates hand you one snapshot and leave. Boltforge stays useful after day one.
| Problem | How Boltforge handles it |
|---|---|
| Boilerplate templates go stale the moment you add feature #2 | Feature generation is a first-class command, not a one-time scaffold |
| Starter kits ship code you never use | Only the strategy and add-ons you choose are generated |
Generators that rewrite app.js and break your edits | Route and permission discovery at startup — no shared file is ever modified |
| A half-written feature after a crash mid-generation | Staging plus a single atomic move; nothing lands unless everything does |
| Inconsistent structure once a team grows | Every feature comes from the same blueprint, so file #50 matches file #1 |
Installation #
Boltforge runs on Node 18 or newer. Install it globally so the boltforge command is available anywhere.
npm install -g boltforge-cli
yarn global add boltforge-cli
pnpm add -g boltforge-cli
# clone the repository, then: cd boltforge-cli npm install npm link # makes `boltforge` available globally
Verify the install:
boltforge-cli version
# 0.1.0import() for its prompt library and modern fs APIs. Node 18 is the minimum; Node 20+ is recommended.Quick Start #
From nothing to a running API with a working feature, in about two minutes.
npm install -g boltforge-cli boltforge-cli create my-api
What happens, step by step
- Boltforge asks you six questions (auth strategy, identifier, notifications, socket, Swagger, package manager).
- It builds a complete file plan in memory — nothing is written yet.
- It checks that no file it plans to write already exists.
- It writes every file into a temporary staging directory.
- Only once all files are staged successfully, it moves them into
./my-apiin one step. - It writes
boltforge.config.jsonrecording the choices you made.
Then run it
cd my-api
npm install
cp .env.example .env # then fill in real values (Windows: copy .env.example .env)
npm run devMONGO_URI and replace the placeholder JWT_ACCESS_SECRET in .env. The app validates its environment at boot and exits with a clear message if something required is missing.Add your first feature
boltforge-cli generate offer
That creates src/modules/offer/ with a model, validator, service, controller and routes, plus a unit test. Restart the server and /offers is live — no file was edited to register it.
More commands to try
boltforge-cli create my-api --auth otp --identifier email --yes boltforge-cli generate offer boltforge-cli generate controller users boltforge-cli add socket boltforge-cli add swagger boltforge-cli doctor
Creating a Project #
boltforge-cli create scaffolds the base project once, at the start. Everything after that is generate and add.
boltforge-cli create <project-name> [options]
The base project always includes: the Express app, environment validation, the error architecture, security middleware, the auth module for your chosen strategy, the user module, the admin area (admin/admins and admin/users), route discovery, a logger, and a test harness.
Non-interactive (CI)
boltforge-cli create my-api --auth otp --identifier email --db mongodb --pm npm --yes
--yes skips every prompt and uses your flags plus defaults. Any flag you pass on its own is also respected in interactive mode — Boltforge only asks about what you did not specify.
--auth password, --identifier email, --db mongodb, --pm npm, and notifications / socket / Swagger all off.Preview without writing
boltforge-cli create my-api --yes --dry-run
Prints the full list of files that would be created and exits. Nothing touches disk.
Interactive Create #
Run boltforge-cli create my-api with no flags and Boltforge walks you through the choices.
$ boltforge-cli create my-api
? Authentication: (Use arrow keys)
❯ Password
OTP (passwordless)
? Identifier: Email
? Enable notifications (FCM)? No
? Enable Socket.io? No
? Enable Swagger UI? No
? Package manager: npm
✔ Project "my-api" created at /path/to/my-api (76 files)
info Next steps:
→ cd my-api
→ npm install
→ cp .env.example .env # then fill in real secrets
→ npm run dev| Prompt | Choices | What it decides |
|---|---|---|
| Authentication | Password · OTP | Which auth module is generated. The other strategy's code is never written. |
| Identifier | Email · Phone · Email + Phone | Shape of the User model and which delivery provider is used. |
| Notifications (FCM) | Yes · No | Whether the notification module and firebase-admin are included. |
| Socket.io | Yes · No | Whether src/sockets/ is created now (you can always boltforge-cli add socket later). |
| Swagger UI | Yes · No | Whether /api-docs is mounted now. |
| Package manager | npm · yarn · pnpm | Recorded in config and used in the printed next-step commands. |
Generate Commands #
Two forms, one engine: a shortcut for a whole feature, or an explicit type when you want a single file.
boltforge-cli generate <name> # shortcut → generate feature <name> boltforge-cli generate <type> <name> # explicit type
The shortcut rule is simple: if generate receives one argument and it is not a known generator type, it is treated as a feature name. Passing a bare type with no name (boltforge-cli generate controller) is reported as a usage error rather than silently creating a feature called "controller".
Available types
| type | Produces | Location |
|---|---|---|
| feature | model + validator + service + controller + routes + unit test | src/modules/<name>/ |
| crud | same as feature, with all five CRUD operations implemented | src/modules/<name>/ |
| admin | feature files + a permissions file, permission-guarded routes | src/modules/admin/<name>/ |
| controller | one controller file | src/modules/<name>/ |
| service | one service file | src/modules/<name>/ |
| model | one Mongoose model file | src/modules/<name>/ |
| validator | one Joi validator file | src/modules/<name>/ |
| middleware | one custom middleware file | src/middlewares/ |
Examples
$ boltforge-cli generate offer
✔ Generated feature "offer" (6 files)
src/modules/offer/offer.model.js
src/modules/offer/offer.validator.js
src/modules/offer/offer.service.js
src/modules/offer/offer.controller.js
src/modules/offer/offer.routes.js
tests/unit/offer.service.test.js$ boltforge-cli generate crud product # identical to: boltforge-cli generate feature product --crud # routes created: POST /products GET /products # paginated GET /products/:id PATCH /products/:id DELETE /products/:id
$ boltforge-cli generate controller users ✔ Generated controller "users" (1 file) src/modules/users/users.controller.js # useful when the rest of the module already exists
$ boltforge-cli generate admin reports
✔ Generated admin "reports" (7 files)
src/modules/admin/reports/reports.model.js
src/modules/admin/reports/reports.validator.js
src/modules/admin/reports/reports.service.js
src/modules/admin/reports/reports.controller.js
src/modules/admin/reports/reports.routes.js
src/modules/admin/reports/reports.permissions.js
tests/unit/admin/reports.service.test.jsFlags
| flag | Effect |
|---|---|
| --crud | Implement all five CRUD operations in the generated service, controller and routes. |
| --no-model | Skip the model file — useful when the feature reuses an existing model. |
| --no-tests | Skip the generated unit test file. |
| --force | Overwrite files that already exist. Without it, a conflict stops the whole generation. |
| --dry-run | Print the planned files and exit without writing. |
generate and add require a boltforge.config.json in the current directory. Outside a Boltforge project they stop immediately with a clear message.Add-ons #
Optional capabilities you can bolt on at any time — not just at create.
boltforge-cli add socket boltforge-cli add swagger
| addon | Adds | Result |
|---|---|---|
| socket | src/sockets/ — gateway, auth middleware, example handler, entry point | Socket.io attaches to the existing HTTP server on next start |
| swagger | src/docs/swagger.js | /api-docs serves every route already documented in your project |
server.js and app.js are written once at create with guarded optional hooks (fs.existsSync checks). Adding socket or Swagger later drops files into place — neither command edits an existing file.Both add-ons support --force and --dry-run. If the add-on is already recorded in boltforge.config.json, Boltforge warns you before proceeding.
Doctor #
A read-only health check for the current project. Run it any time — especially before deploying.
$ boltforge-cli doctor
⚠ config says socket is enabled, but src/sockets/index.js is missing.
✖ JWT_ACCESS_SECRET is still the placeholder value from .env.example — replace it before running in production.
⚠ node_modules not found — run npm/yarn/pnpm install.What it checks
- Node version meets the minimum required by generated projects.
- Core module directories recorded in config actually exist on disk.
- Socket / Swagger flags in config match the files present.
.envexists and no longer contains the placeholder JWT secret.package.jsonis present and dependencies are installed.- Whether the last generation finished or was interrupted.
Warnings are informational; errors set a non-zero exit code, so boltforge-cli doctor works as a CI gate. Pass --repair to resume an interrupted generation instead of regenerating from scratch.
Project Structure #
Click any file or folder to see what it does, what it depends on, and who uses it.
Select a file or folder from the tree to see its details.
The layout is feature-first: each feature owns one folder containing its model, validator, service, controller and routes. Generating a feature touches exactly one directory instead of scattering files across controllers/, services/ and models/.
Configuration #
Two files, two clearly separated jobs — they never overlap.
| File | Owns | Read by |
|---|---|---|
| .env | Runtime behaviour: database URI, JWT secret, SMTP/SMS credentials, port | The running application |
| boltforge.config.json | What was generated and with which options | The boltforge CLI only |
No secret ever appears in boltforge.config.json, and the application never reads it at runtime.
boltforge.config.json
{
"cliVersion": "0.1.0",
"createdAt": "2026-01-15T10:00:00.000Z",
"auth": { "strategy": "otp", "identifier": "email" },
"database": "mongodb",
"notifications": { "enabled": true, "provider": "fcm" },
"socket": false,
"swagger": false,
"packageManager": "npm",
"modules": ["auth", "user", "admin"],
"lastGeneration": null
}Environment validation
src/config/env.js validates process.env against a Joi schema at boot. A missing or malformed required variable exits immediately with a readable message rather than failing mysteriously later.
NODE_ENV=development PORT=3000 MONGO_URI=mongodb://127.0.0.1:27017/my-api JWT_ACCESS_SECRET=replace-with-a-long-random-string JWT_ACCESS_EXPIRES_IN=15m
Authentication #
You pick one strategy at create time. Only that strategy's code is generated.
Both strategies share the same token foundation: a short-lived access JWT plus a rotating refresh token stored server-side. Neither strategy talks to an email or SMS vendor directly — they call a delivery abstraction instead.
Auth service
↓
VerificationService // sendOtp / sendPasswordReset / sendVerification
↓
EmailProvider | SmsProvider // "send this text to this address" — nothing moreSwapping SendGrid for SES, or Twilio for another gateway, means editing one provider file. The auth service is untouched because it never knew which vendor was in use.
Password Authentication #
Generated when you choose --auth password.
| Endpoint | Purpose |
|---|---|
| POST /auth/register | Create an account; password hashed with bcrypt (cost 12) |
| POST /auth/login | Returns an access token and a refresh token |
| POST /auth/refresh | Rotates the refresh token and issues a new access token |
| POST /auth/logout | Revokes the caller's refresh tokens |
| POST /auth/forgot-password | Issues a single-use reset token via the delivery layer |
| POST /auth/reset-password | Consumes the reset token and sets a new password |
| POST /auth/change-password | Requires the current password; authenticated route |
forgot-password responds identically whether or not the account exists, so the endpoint cannot be used to discover registered identifiers.OTP Authentication #
Generated when you choose --auth otp. Passwordless: no password field is written to the User model at all.
POST /auth/request-otp // 6-digit code, hashed, 5-minute TTL POST /auth/verify-otp // atomic single-use consume ├── known user → { accessToken, refreshToken, sessionId: null } └── new identity → { accessToken: null, refreshToken: null, sessionId } POST /auth/complete-registration // consumes sessionId, creates the user
Why the two-step signup
Verifying a code proves the person controls that email or phone, but it does not yet mean an account exists. Rather than half-creating a user, Boltforge issues a short-lived sessionId (10 minutes) that carries only "this identifier was verified". Your registration endpoint exchanges it for a real account along with whatever extra profile fields you require.
Safeguards
- Codes are stored hashed, never in plain text.
- Consuming a code is an atomic find-and-mark, so two concurrent requests cannot both succeed.
- Expired records are removed automatically by a MongoDB TTL index.
/auth/request-otpand/auth/verify-otpsit behind a stricter rate limiter than the rest of the API.
Email / Phone / Both #
--identifier decides the shape of the User model and which provider the delivery layer picks.
| --identifier | User model | Delivery |
|---|---|---|
identifier + identifierType: 'email' | EmailProvider | |
| phone | identifier + identifierType: 'phone' | SmsProvider |
| both | identifiers[] — array of { type, value, verified } | Chosen per request from the identifier's type |
The both mode is for products where a user may sign up with one channel and later attach the other — for example registering by email and adding a phone number for account recovery. The provider decision lives in one place (resolveProvider), never duplicated across the auth service.
Roles & Permissions #
Flat roles on the user, fine-grained permission strings alongside them.
| role | Meaning |
|---|---|
| super_admin | Bypasses every authorization check. Never stores a permission list. |
| admin | May create assistant admins and grant permissions, including admins.*. |
| assistant_admin | Holds exactly the permissions granted to them — never admins.*. |
| user | Default role for end users. |
Permission strings
Permissions are namespaced <resource>.<action>, for example users.read, users.ban, offers.delete, notifications.send, admins.create.
src/constants/permissions.js recursively discovers every *.permissions.js file under src/modules/ and merges them at require time. Generating a new admin feature adds its permissions automatically — no shared catalog file is ever hand-edited.The admins.* rule
The admins namespace is reserved for super_admin and admin. This is enforced inside admins.service.js at the moment permissions are granted — not merely documented — so even a direct service call cannot escalate an assistant admin.
Admin Generator #
The admin area is feature-first too, so it grows without becoming a pile of loose files.
src/modules/admin/ ├── admins/ # admin & assistant-admin management (created by `create`) ├── users/ # end-user management (created by `create`) └── reports/ # ← boltforge-cli generate admin reports
boltforge-cli generate admin reports
This reuses the very same controller / service / model / validator / routes / test generators as a normal feature. A moduleDir parameter nests the output under admin/ and turns on the permission guard — there is no special case hard-coded into the CLI.
| What you get | Detail |
|---|---|
| Route prefix | /admin/reports, written explicitly into the routes file |
| Permissions | reports.read, reports.create, reports.update, reports.delete |
| Guarding | Every route requires authentication plus the matching permission |
| Tests | tests/unit/admin/reports.service.test.js |
roles + permissions. super_admin already bypasses everything, and gating on the permission alone is what lets an assistant admin actually use a feature they were granted.Notifications / FCM #
Included when you pass --notifications. Firebase stays behind an interface.
NotificationService // the only module the rest of your code imports ↓ NotificationProvider // interface: sendToTokens / sendToTopic / (un)subscribeToTopic ↓ FCMProvider // the only file that imports firebase-admin
| NotificationService method | Purpose |
|---|---|
| sendToUser(userId, payload) | Every registered device for one user |
| sendToUsers(userIds, payload) | Batch send across many users |
| sendToTopic(topic, payload) | Broadcast to an FCM topic |
| registerToken / removeToken | Device token lifecycle |
| subscribeToTopic / unsubscribeFromTopic | Topic membership for a user's devices |
Multiple devices and dead tokens
Each device token is its own FcmToken document keyed by user, so one account can have many devices. When FCM reports a token as unregistered, NotificationService — not the provider — deletes it. Cleaning the database is an application concern, so it does not belong inside a vendor adapter.
await notificationService.sendToUser(userId, { notification: { title: 'Offer accepted', body: 'Your offer was accepted.' }, data: { offerId: 'abc123' } });
FIREBASE_SERVICE_ACCOUNT_PATH in .env to point at your service-account JSON. Never commit that file.Socket #
Real-time without coupling your business logic to Socket.io.
Service Client
│ eventBus.emit('offer.created', payload) ▲
▼ │ io.emit(...)
Domain Event Bus ──────────────────────► SocketGateway
(plain EventEmitter) (only file importing socket.io)Services emit domain events on a plain Node EventEmitter. The gateway listens and translates those into socket emissions. Nothing in src/modules/ ever imports socket.io, so removing the add-on later cannot break your services — the events simply have no listener.
| File | Role |
|---|---|
| src/sockets/index.js | Entry point; attaches to the existing HTTP server |
| src/sockets/gateway.js | Creates the Socket.io server, bridges the event bus |
| src/sockets/socket.middleware.js | Verifies the same access JWT during the handshake |
| src/sockets/handlers/ | Client → service direction, like HTTP controllers |
Socket.io shares the application's HTTP server rather than opening a second port, so it inherits the same TLS termination and deployment setup.
Swagger #
Order does not matter: features generated before you add Swagger still show up.
boltforge-cli generate offer # writes @swagger JSDoc above the routes — always boltforge-cli add swagger # mounts /api-docs and reads ALL existing JSDoc
Every generated routes file carries @swagger annotations from the moment it is created. They are plain comments with zero runtime cost, so writing them unconditionally is free. Adding the add-on only turns on the reader — it never rewrites your existing routes.
boltforge-cli add swagger: all ten appear at /api-docs immediately. No regeneration needed.Testing #
Jest, plus an in-memory MongoDB so tests need no running database.
npm test npm run test:watch
| Path | Contains |
|---|---|
| tests/unit/ | Service-level tests. Services take plain objects and never touch req/res, so they test cleanly in isolation. |
| tests/integration/ | Supertest against the Express app directly — no server needs to be listening. |
| tests/e2e/ | Reserved for your own end-to-end scenarios. |
| tests/setup.js | Boots mongodb-memory-server, clears collections between tests, tears down after. |
generate feature, generate crud and generate admin each write a unit test alongside the feature by default. Pass --no-tests to skip it.
Error Handling #
One response contract, one place that decides it.
{
"success": true,
"message": "Offer created",
"data": { "_id": "..." }
}{
"success": false,
"message": "Validation failed",
"error": {
"code": "VALIDATION_ERROR",
"details": { "email": "must be a valid email" }
}
}| Error class | status | code |
|---|---|---|
| ValidationError | 400 | VALIDATION_ERROR |
| AuthenticationError | 401 | AUTHENTICATION_ERROR |
| AuthorizationError | 403 | AUTHORIZATION_ERROR |
| NotFoundError | 404 | NOT_FOUND |
| ConflictError | 409 | CONFLICT |
| anything unexpected | 500 | INTERNAL_ERROR |
Services throw; controllers do not catch. asyncHandler forwards rejections to the global error middleware, which calls classifyError() — the single function that maps any thrown value onto the contract above.
Mongoose errors are translated, not leaked
CastError becomes a 400, duplicate-key 11000 becomes a 409, and Mongoose validation errors become field-level details. Raw driver messages never reach the client.
logs/error.log only. The client receives a message, a code, and sanitized details — never internals.Security #
Defaults chosen so a fresh project is not insecure on day one.
| Concern | Handling |
|---|---|
| HTTP headers | helmet |
| Cross-origin | cors, explicitly configured |
| Parameter pollution | hpp |
| NoSQL injection | express-mongo-sanitize |
| Brute force | General limiter, plus a stricter one on login / OTP endpoints |
| Password storage | bcrypt, cost factor 12 |
| Input validation | Joi schemas at the route boundary |
| Secrets | .env only, git-ignored; doctor flags placeholder values |
Token theft detection
Refresh tokens belong to a family — the chain of tokens issued by successive rotations of one login session. Rotating a token retires the old one. If a retired token is presented again, that is a strong signal it was stolen: the entire family is revoked immediately and the event is written to logs/security.log.
OneTimeTokenStore // OTP, password reset — used once, then dead RefreshTokenStore // issue / rotate / revokeFamily / reuse detection
The two stores are deliberately separate contracts. A reused OTP is just an error; a reused refresh token is a security event. Collapsing them into one interface would blur that distinction.
Generator Engine #
Every command runs through the same four pieces.
Registry
Scans src/generators/* at startup and registers whatever exports a type and a plan().
Template engine
Handlebars, deliberately logic-less. Decisions are resolved in the generator and passed in as booleans.
File plan
plan() is pure — it returns { targetPath, content }[] and performs no I/O.
File writer
Detects conflicts, stages every file, then commits with one atomic move.
A generator is one file
// src/generators/<type>/generator.js module.exports = { type: 'repository', // how it's invoked on the CLI describe: '...', plan(name, options, ctx) { // pure: returns FilePlan[] return [{ targetPath, content }]; } };
Dropping that folder in is the whole installation step. generate.command.js, the argument parser and the registry contain no list of type names to update — they only ask the registry what exists.
routes, test, permissions, project, socket, swagger) are building blocks used by blueprints rather than commands you type. The public generate types are: feature, crud, admin, controller, service, model, validator, middleware.Feature Blueprint #
Blueprints compose existing generators. They never duplicate file-writing logic.
boltforge-cli generate offer
↓ not a known type → generate feature offer
↓ Feature Blueprint composes:
model.plan() + validator.plan() + service.plan()
+ controller.plan() + routes.plan() + test.plan()
↓
ONE combined FilePlan[] → ONE staging dir → ONE atomic moveBecause the whole feature commits as a single unit, there is no state where the controller landed but the model did not. The admin blueprint works identically, adding permissions.plan() and passing moduleDir: 'admin'.
Route Discovery #
A feature becomes reachable because its file exists — not because something registered it.
At boot, src/routes/index.js recursively finds every *.routes.js anywhere under src/modules/, sorts them by path for deterministic ordering, and mounts each at the prefix it exports.
module.exports.prefix = '/offers'; // written explicitly by the generator module.exports.router = router;
Recursion matters: admin sub-features live two levels deep at src/modules/admin/reports/. The prefix is written into the file rather than inferred, so pluralization is never guessed at runtime.
Failure policy
| Environment | If a route file fails to load |
|---|---|
| development | Fail-soft: log a clear warning naming the module, mount everything else, keep serving |
| production | Fail-hard: abort startup — silently serving without a route is worse than a visibly failed deploy |
Override explicitly with STRICT_ROUTE_DISCOVERY=true|false in .env.
Dependency Resolver #
Your package.json is computed from your choices, not copied from a fixed template.
Each blueprint declares its own dependencies in isolation. The resolver merges every selected set into one manifest, keyed by package name so a package can never be added twice. When two blueprints ask for different versions of the same package it keeps the higher range and prints a warning — it never resolves a conflict silently.
| Selection | Adds |
|---|---|
| base (always) | express, mongoose, dotenv, joi, winston, helmet, cors, hpp, express-rate-limit, express-mongo-sanitize, jsonwebtoken |
--auth password | bcrypt, nodemailer |
--auth otp | nodemailer, twilio |
--notifications | firebase-admin |
--socket | socket.io |
--swagger | swagger-jsdoc, swagger-ui-express |
| dev (always) | jest, supertest, mongodb-memory-server |
bcrypt never appears in your manifest. Skip notifications and firebase-admin is never installed.Lifecycle #
Every generation follows the same ordered pipeline.
validate → plan → conflict detection → [--dry-run stops here] → write to temp staging directory // project untouched so far → atomic move into the project // ← the single commit point → update boltforge.config.json → install dependencies → format → lint
| Failure point | Result |
|---|---|
| Anywhere before the atomic move | Project is completely untouched. Rollback is free — nothing real happened yet. |
| After the atomic move | Files stay. Status is recorded as partial and boltforge-cli doctor --repair resumes the remaining steps. |
Dry Run #
See the plan before anything touches disk. Supported by create, generate and add.
$ boltforge-cli generate crud product --dry-run
info Dry run — 6 file(s) would be generated:
src/modules/product/product.model.js
src/modules/product/product.validator.js
src/modules/product/product.service.js
src/modules/product/product.controller.js
src/modules/product/product.routes.js
tests/unit/product.service.test.jsDry run happens after conflict detection, so it also tells you whether the generation would collide with existing files.
Conflict Detection #
Boltforge never overwrites your work silently.
$ boltforge-cli generate offer # when offer/ already exists
✖ Generation stopped — 6 file(s) already exist (use --force to overwrite):
src/modules/offer/offer.model.js
src/modules/offer/offer.validator.js
...The check runs across the entire plan before any file is written. One existing file stops the whole generation — you never end up with a half-updated feature. Pass --force to overwrite deliberately.
Examples #
A jobs platform, from scratch
# passwordless auth by phone, with push notifications and realtime boltforge-cli create jobs-api --auth otp --identifier phone --notifications --socket --pm pnpm --yes cd jobs-api && pnpm install # core domain boltforge-cli generate crud job boltforge-cli generate crud application boltforge-cli generate company --no-model # reuses an existing model # admin side boltforge-cli generate admin moderation boltforge-cli generate admin analytics # docs last — existing routes still show up boltforge-cli add swagger boltforge-cli doctor
Preview a risky regeneration
boltforge-cli generate crud job --dry-run # check the collision list
git add -A && git commit -m "before regen"
boltforge-cli generate crud job --forceCI pipeline
npm install -g boltforge-cli
boltforge-cli create ci-api --auth password --identifier email --yes
cd ci-api && npm install
boltforge-cli doctor # non-zero exit on errors
npm testTroubleshooting #
“No boltforge.config.json found here”
generate and add must run from a project's root — the directory holding boltforge.config.json. Run cd my-api first, or boltforge-cli create if you have not scaffolded yet.
“<type> is a generator type, not a feature name”
You ran something like boltforge-cli generate controller with no name. Add the name: boltforge-cli generate controller users.
Generation stopped because files already exist
Expected behaviour — see Conflict Detection. Inspect the list, commit your work, then re-run with --force if you really want to overwrite.
Server exits at boot with an environment error
src/config/env.js validates required variables before anything starts. Copy .env.example to .env and fill in at least MONGO_URI and a real JWT_ACCESS_SECRET (16+ characters).
A route returns 404 even though the feature exists
Routes are discovered at startup — restart the server after generating. Then confirm the file is named *.routes.js, lives under src/modules/, and exports both router and prefix. In development a failed module logs a warning rather than crashing, so check the startup log.
403 on an admin route despite having the role
Check whether the route requires a permission as well. When a requirement lists both roles and permissions, both must be satisfied. Grant the specific permission to the account, or use super_admin, which bypasses all checks. See Authorization.
Refresh suddenly fails with “suspicious activity”
A refresh token that was already rotated was presented again, so the whole token family was revoked by design. Usually this means an old client copy replayed a stale token. The user must log in again; check logs/security.log for the event.
Swagger shows nothing at /api-docs
Confirm src/docs/swagger.js exists (boltforge-cli add swagger) and restart. Routes appear from their @swagger JSDoc comments, which generated route files already include.
Windows: “boltforge is not recognized”
The global npm bin directory is not on your PATH. Run npm config get prefix and add that folder to PATH, or invoke it directly via npx boltforge .... If you installed from source, make sure npm link completed without errors.
CLI Reference #
Every command and flag Boltforge implements today.
Scaffolds a new base project. Run once per project.
| flag | values | Description |
|---|---|---|
| --auth | password | otp | Auth strategy. Only the chosen one is generated. Default password under --yes. |
| --identifier | email | phone | both | User identifier shape. Default email under --yes. |
| --db | mongodb | Database. Only mongodb is supported; any other value is rejected. |
| --notifications | flag | Include the FCM notification module. |
| --socket | flag | Include Socket.io immediately. |
| --swagger | flag | Include Swagger UI immediately. |
| --pm | npm | yarn | pnpm | Recorded in config and used in printed next steps. |
| --force | flag | Overwrite existing files. |
| --dry-run | flag | Print the file plan and exit. |
| --yes | flag | Skip all prompts; use flags and defaults only. |
boltforge-cli create my-api --auth otp --identifier email --db mongodb --pm npm --yes
Generates a feature or a single file inside an existing project. One argument that is not a known type is treated as a feature name.
Types: feature, crud, admin, controller, service, model, validator, middleware
| flag | Description |
|---|---|
| --crud | Implement all five CRUD operations. |
| --no-model | Skip the model file. |
| --no-tests | Skip the generated test file. |
| --force | Overwrite existing files. |
| --dry-run | Print the file plan and exit. |
boltforge-cli generate offer boltforge-cli generate controller users boltforge-cli generate crud product --no-tests boltforge-cli generate admin reports
Adds an optional capability. Addons: socket, swagger. Supports --force and --dry-run.
boltforge-cli add socket boltforge-cli add swagger --dry-run
Diagnoses the current project. Errors produce a non-zero exit code.
| flag | Description |
|---|---|
| --repair | Resume the last unfinished generation rather than starting over. |
boltforge-cli doctor boltforge-cli doctor --repair
boltforge-cli version prints the installed version; boltforge-cli --version and -V do the same. boltforge-cli help [command] lists all commands, or details one command's flags.
boltforge-cli version boltforge-cli help generate
FAQ #
Is Boltforge a framework? Do I depend on it at runtime?
No. It generates plain Express/Mongoose JavaScript that you own. Uninstall the CLI and your project still runs, tests and deploys exactly the same.
Can I edit generated code?
Yes — that is the point. Boltforge never reads your files back or re-syncs them. The only time it touches an existing file is when you explicitly pass --force.
Can I switch auth strategy after creating the project?
Not with a command. The choice determines which module was generated, and Boltforge does not rewrite existing modules. For an early-stage project, regenerating is usually simpler; later on, adding the second strategy by hand against the same token stores is straightforward.
Does it support SQL, TypeScript, or GraphQL?
Not today. Output is JavaScript on Express + MongoDB. The generator engine is type-agnostic, so those are plausible future generators rather than architectural blockers.
How do I add my own generator?
Create src/generators/<type>/generator.js exporting type, describe and a pure plan(). The registry discovers it at startup — no CLI file needs editing. See Generator Engine.
Why are roles and permissions an AND when combined?
Because the restrictive reading is the safe default: authorize({ roles: ['admin'], permissions: ['admins.create'] }) means admin and holding that permission. For an either/or gate, require the permission alone — super_admin always bypasses.
Is generated code overwritten when I upgrade Boltforge?
No. Boltforge only writes when you run a command, and only to the paths in that command's plan. Upgrading the CLI affects future generations only.