create-express-auth-app
v1.0.0
Published
Scaffold a ready-to-run Node/Express JWT authentication backend (MongoDB or PostgreSQL, JavaScript or TypeScript, optional Google OAuth and TOTP 2FA) with one command.
Maintainers
Readme
create-express-auth-app
Scaffold a production-style Node.js + Express JWT authentication backend in one command.
Stop rewriting the same login, refresh-token, email-verification and password-reset code for every project. create-express-auth-app generates a complete, working auth backend into a folder you own: routes, controllers, services, models, middleware, validation, configuration and a ready-to-use .env.
npx create-express-auth-app my-appFeatures
- Two databases: MongoDB (Mongoose) or PostgreSQL (Drizzle ORM)
- TypeScript or JavaScript: pick your language; the JavaScript output is generated from the same source, so both stay identical in behavior
- Email verification: a clickable link, or a 6-digit code
- Two-factor authentication (optional): TOTP for authenticator apps such as Google Authenticator or Authy, with QR-code setup
- Google sign-in (optional): verifies Google ID tokens server-side
- Secure sessions: short-lived access token plus rotating refresh token, both in
httpOnly,SameSite=Strictcookies, never exposed to browser JavaScript - Safe by default: bcrypt-hashed passwords, hashed refresh and verification tokens at rest, rate-limited login and reset endpoints, Zod input validation, no user-enumeration leaks
- Works out of the box: secrets are generated for you, and emails are printed to the console when SMTP isn't configured, so you can test the whole flow locally
- You own the code: the generated project has no runtime dependency on this package
Requirements
- Node.js 20.12 or newer
- A running MongoDB or PostgreSQL instance for the database you choose
Quick start
npx create-express-auth-app my-app
cd my-appAnswer the prompts, then:
# PostgreSQL only: create and apply the tables
npm run db:generate
npm run db:migrate
npm run devThe API is now running on http://localhost:3000. Check it with:
curl http://localhost:3000/api/healthMongoDB projects need no migration step.
Prompts
| Prompt | Options | Default |
| ------------------------ | ----------------------------- | ------------- |
| Project directory | any path | required |
| Project name | valid npm package name | folder name |
| Package manager | npm, pnpm, yarn | npm |
| Language | TypeScript, JavaScript | TypeScript |
| Database | MongoDB, PostgreSQL | MongoDB |
| Email verification | link, 6-digit code | link |
| Google OAuth | yes / no | no |
| Two-factor auth (TOTP) | yes / no | no |
| Connection string | MongoDB URI or PostgreSQL URL | local default |
| Install dependencies now | yes / no | yes |
Non-interactive usage
Every prompt has a flag, which makes the CLI usable in scripts and CI.
npx create-express-auth-app my-app \
--db postgres --lang ts --verify code --google --2fa --pm pnpm --yes| Flag | Values | Description |
| -------------------------- | --------------------- | ------------------------------------------------------ |
| --db | mongo, postgres | Database |
| --lang | ts, js | Output language |
| --verify | link, code | Email verification method |
| --google / --no-google | | Include or skip Google OAuth |
| --2fa / --no-2fa | | Include or skip TOTP two-factor auth |
| --pm | npm, pnpm, yarn | Package manager used to install |
| --no-install | | Skip dependency installation |
| --yes | | Skip all prompts; unset options use the defaults above |
API reference
Base path: /api
| Method | Endpoint | Auth | Description |
| ------- | ----------------------------- | -------- | ----------------------------------------------------------------- |
| GET | /health | | Health check |
| POST | /auth/register | | Create an account and send a verification message |
| GET | /auth/verify-email/:token | | Verify email (link mode); logs the user in |
| POST | /auth/verify-email | | Verify email with { email, code } (code mode); logs the user in |
| POST | /auth/resend-verification | | Send a new verification message |
| POST | /auth/login | | Log in; sets cookies |
| POST | /auth/refresh-token | cookie | Rotate the refresh token and issue a new access token |
| POST | /auth/logout | required | Log out and revoke refresh tokens |
| POST | /auth/forgot-password | | Send a password reset link |
| GET | /auth/reset-password/:token | | Check that a reset link is still valid |
| PATCH | /auth/reset-password/:token | | Set a new password |
| GET | /users/me | required | Get the current user's profile |
| PATCH | /users/me | required | Update name, phone and address |
| POST | /auth/google | | Log in with a Google ID token (Google option) |
| POST | /auth/2fa/setup | required | Generate a secret and QR code (2FA option) |
| POST | /auth/2fa/enable | required | Confirm setup with a 6-digit code (2FA option) |
| POST | /auth/2fa/disable | required | Turn 2FA off; needs a current { code } (2FA option) |
| POST | /auth/2fa/verify-login | | Finish a 2FA login with { pendingToken, code } (2FA option) |
Verification link and code modes each expose only their own verify-email route.
Example
# Register
curl -X POST http://localhost:3000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"Ada","email":"[email protected]","password":"correct-horse-battery"}'
# Log in and keep the cookies
curl -i -c cookies.txt -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"correct-horse-battery"}'
# Call a protected route with the cookies
curl -b cookies.txt http://localhost:3000/api/users/meWith two-factor auth enabled for a user, login returns { "twoFactorRequired": true, "pendingToken": "..." } instead of cookies. Send that token and a code from the authenticator app to /auth/2fa/verify-login to finish logging in.
What gets generated
my-app/
├── src/
│ ├── app.ts Express app, CORS, error handler
│ ├── index.ts Server entry point
│ ├── constants/ Roles, cookie names, token lifetimes
│ ├── configs/ Cookie options, database connection
│ ├── models/ Mongoose models (MongoDB)
│ ├── db/schema.ts Drizzle tables (PostgreSQL)
│ ├── schemas/ Zod request validation
│ ├── middlewares/ JWT auth, role guard, rate limiters
│ ├── controllers/ Request handlers
│ ├── services/ Business logic and database access
│ ├── routes/ Route definitions
│ └── utils/ Token helpers, HTTP error class
├── drizzle.config.ts drizzle-kit config (PostgreSQL)
├── .env Generated, with random secrets (git-ignored)
├── .env.example Same keys without values, safe to commit
├── .gitignore
├── tsconfig.json TypeScript only
├── package.json
└── README.md Project-specific instructionsEnvironment variables
| Variable | Purpose |
| --------------------------------------------------------------- | ------------------------------------------------------------------------- |
| PORT | Server port (default 3000) |
| NODE_ENV | production turns on the Secure cookie flag |
| MONGO_URI / DATABASE_URL | Database connection string |
| CLIENT_URL | Frontend origin allowed by CORS |
| CLIENT_BASE_URL | Base URL used in email links |
| SALT_ROUNDS | bcrypt cost factor || JWT_ACCESS_SECRET, JWT_REFRESH_SECRET | Token signing secrets |
| REFRESH_TOKEN_HASH_SECRET, EMAIL_TOKEN_HASH_SECRET | Keys for hashing stored tokens |
| SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM | Outgoing email. Leave blank in development to print emails to the console |
| GOOGLE_CLIENT_ID | Only with the Google option |
The four secrets are filled with random values when the project is created.
Security notes
- Access tokens last 15 minutes and refresh tokens 10 days. Refresh tokens are rotated on every use, and only a hash of each is stored.
- Rate limits per IP: login 5 per 15 minutes; registration 5 per hour; password reset and resend-verification 3 per hour each; code-based email verification and 2FA codes 10 per 15 minutes.
- Every JWT carries a
purposeclaim that is checked on verification, so a 2FA pending token can't be used as an access token. - Disabling 2FA needs a current authenticator code, not just a session.
- Google sign-in only accepts accounts whose email Google has verified.
- Passwords are always hashed before they are saved, through a single code path in each database variant.
- Registration, password reset and resend-verification responses are identical whether or not the account exists.
- The server refuses to start if
CLIENT_URLor any of the secrets are missing. - Cookies are
httpOnlyandSameSite=Strict, so tokens are not readable from browser JavaScript.
Known simplifications
These are deliberate defaults you can change in the generated code:
- Logging out signs the user out of every device, not just the current one.
- One active refresh token per user, rotated in place, rather than one per session.
- PostgreSQL does not delete expired token rows automatically. Every lookup checks the expiry time, so expired tokens are never accepted, but old rows stay until you prune them (for example with a scheduled
DELETE ... WHERE expires_at < now()). MongoDB removes them itself with TTL indexes. - Roles are stored (
customer,staff,admin) and a role guard middleware is included, but no admin or staff routes are generated.
FAQ
Does my generated project depend on create-express-auth-app?
No. It only uses regular packages such as Express, Zod, and Mongoose or Drizzle. You can delete this CLI after scaffolding.
Can I use my own SMTP provider?
Yes. Fill in the SMTP_* variables. Any provider that supports SMTP works, such as Resend, Postmark, SendGrid or Gmail app passwords.
Why does the console show links instead of sending email? With no SMTP settings, the app prints verification links and codes to the console so you can develop without an email account.
Can I add the Google or 2FA option later? Not through the CLI. Scaffold a new project with the option on and copy the extra files across.
Contributing
Issues and pull requests are welcome at github.com/mrrauf99/create-express-auth-app.
git clone https://github.com/mrrauf99/create-express-auth-app.git
cd create-express-auth-app
npm install
npm link
create-express-auth-app ../scratch-app
npm test # scaffolds every option combination and checks the output
TYPECHECK=1 npm test # also installs deps and type-checks the generated TypeScript (slow)To release, bump version in package.json, update CHANGELOG.md, then publish a GitHub release tagged v<version> (for example v1.0.1). The Release workflow reruns CI and publishes to npm with provenance. Versions with a pre-release suffix such as 1.1.0-beta.1 go to the next tag instead of latest.
Templates live in templates/ and are written once in TypeScript: shared/ is always included, then one database (mongo/ or postgres/), one verification method (verify-link/ or verify-code/), and the optional google-oauth/ and two-factor/ layers. bin/lib/scaffold.js combines the selected layers and, for JavaScript output, removes the type annotations with esbuild.
