🗂️ Fastify User Management Boilerplate
A production-ready back-office boilerplate for user account management, built with Fastify, Prisma, Zod, and PostgreSQL. Designed to be forked and extended as the backend foundation of any application requiring authentication and account lifecycle management.
✨ Features
- Authentication – JWT-based auth with cookie support and token versioning (revocation on password change)
- Registration & Login – Secure password hashing with Argon2
- Google OAuth – Login or register via Google access token, with automatic account merge detection
- Password recovery – Forgot password flow with time-limited action tokens
- Password change – Authenticated password update with session revocation
- Email change – Two-step flow with a 15-minute confirmation window and a 24-hour rollback period
- Account deletion – Soft-delete with a 7-day cancellation window
- Input validation – Zod schemas with shared password rules
- Email notifications – Nodemailer integration (confirmation, recovery, alerts)
- Scheduled tasks – node-cron for background jobs (e.g. purging expired accounts)
- Security-first error codes – Intentionally vague error responses to prevent user enumeration
🧱 Stack
| Layer | Technology |
|---|---|
| Framework | Fastify v5 |
| ORM | Prisma v7 |
| Database | PostgreSQL (via pg + @prisma/adapter-pg) |
| Validation | Zod v4 |
| Auth | @fastify/jwt + @fastify/cookie |
| Password hashing | Argon2 |
| Nodemailer | |
| Scheduled jobs | node-cron |
| Runtime | Node.js + tsx |
| Language | TypeScript |
📁 Project Structure
src/
├── app.ts # Fastify app factory & plugin registration
├── server.ts # Entry point
├── routes/ # Route definitions (declared before services)
├── services/ # Business logic
├── schemas/ # Zod validation schemas (incl. shared.schema.ts)
├── plugins/ # Fastify plugins (JWT, cookies, DB, etc.)
├── middleware/ # Auth guards and request hooks
├── errors/ # Custom error classes and error codes
├── types/ # TypeScript type definitions
└── generated/ # Prisma generated client
🚀 Getting Started
Prerequisites
- Node.js ≥ 20
- PostgreSQL database
- An SMTP server (or service like Mailtrap / Resend for dev)
Installation
git clone https://github.com/your-username/your-repo.git
cd your-repo
npm install
Environment
Rename .env.example to .env and fill in the values:
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/dbname"
# Email
SMTP_HOST="smtp.example.com"
SMTP_PORT=587
SMTP_USER="your@email.com"
SMTP_PASS="your-smtp-password"
MAIL_FROM="no-reply@example.com"
# URLs
APP_URL=BACK_OFFICE_SERVER_URL
FRONT_URL=YOUR_FRONT_URL # Used for links in emails
# Secrets
JWT_SECRET="your-jwt-secret"
Database
npx prisma migrate dev
Run (development)
npm run dev
🔐 Auth Flow Overview
- On login, a JWT is issued (stored in an HTTP-only cookie) containing the user's ID and a
tokenVersion. - On password change,
tokenVersionis incremented, invalidating all previously issued tokens. - Action tokens (password reset, email confirmation, etc.) are single-use and deleted on consumption.
- The
langfield on requests is derived from theAccept-Languageheader (defaults tofr).
Google OAuth
The Google flow accepts an access_token obtained by the client after the Google sign-in, and exchanges it for user info via the Google UserInfo API. Three cases are handled:
| Situation | Behaviour |
|---|---|
Email exists, no googleId |
Returns mergeRequired: true — the client must prompt the user to link their accounts |
| Existing Google account | Logs the user in directly (checks for pending deletion) |
| New user | Creates the account with googleId, picture and display name from Google; sends a confirmation email if the address is not already verified by Google |
User preferences (language, theme) are automatically created on first Google sign-in.
🛣️ API Routes
Auth — /auth
| Method | Route | Auth | Description |
|---|---|---|---|
POST |
/auth/register |
— | Create a new account |
POST |
/auth/login |
— | Login and receive a JWT cookie |
POST |
/auth/logout |
✅ | Invalidate the session cookie |
POST |
/auth/google |
— | Login or register via Google access token |
User — /user
| Method | Route | Auth | Description |
|---|---|---|---|
GET |
/users |
— | List all users |
GET |
/user/confirm?token= |
— | Confirm email address |
PATCH |
/user/display-name |
✅ | Update display name |
POST |
/user/pwd-recovery-request |
— | Request a password reset email |
POST |
/user/pwd-recovery |
— | Reset password via token |
PATCH |
/user/pwd-change |
✅ | Change password (re-issues JWT cookie) |
POST |
/user/email-change-request |
✅ | Request an email change (sends confirmation to new address) |
GET |
/user/email-change-confirm?token= |
— | Confirm the new email address |
GET |
/user/email-change-rollback?token= |
— | Rollback to previous email (24h window) |
DELETE |
/user/account |
✅ | Schedule account deletion (7-day window) |
GET |
/user/delete-cancel?token= |
— | Cancel scheduled account deletion |
✅ = requires a valid
authTokencookie
📋 Available Scripts
npm run dev # Start development server with tsx
npm run build # Compile TypeScript to dist/
npm run start # Run compiled server
📄 License
Copyright (c) 2026 Raffi (& Claude ai)
Permission is hereby granted to use, copy, modify, and distribute this software for non-commercial purposes only, with attribution.
Commercial use of any kind is strictly prohibited without prior written permission from the author.