A production-ready Node.js REST API boilerplate built with Express 5, MongoDB, JWT authentication, Socket.IO, and essential service integrations. Clone it, configure your .env, and ship.
- Express 5 — latest stable with improved async error handling
- MongoDB + Mongoose 9 — ODM with
toJSONandpaginateplugins - ES Modules — native
import/export, no transpile step - Node.js 20+ — uses built-in
crypto, no extra date libraries
- JWT — access + refresh token pattern with blacklisting
- Role-based access control —
user,adminroles via Passport.js - OTP verification — email and phone OTP with
crypto.randomInt()(cryptographically secure) - Bcrypt — password hashing at cost factor 12
- Helmet — HTTP security headers
- Rate limiting —
express-rate-limitv8 on auth routes - MongoDB sanitization —
express-mongo-sanitize - CORS — configurable per environment
- Socket.IO 4 — WebSocket support for chat and live notifications
- Email — Nodemailer with SMTP; auto-falls back to Ethereal Email in dev
- SMS — pluggable SMS service (mock by default)
- File storage — Cloudinary integration
- Payments — Razorpay integration
- Search — Elasticsearch 9 integration
- ESLint 9 + Prettier — enforced code style
- Husky — pre-commit linting via git hooks
- Jest 29 — unit and integration tests with MongoDB Memory Server
- Nodemon — dev auto-restart
- Swagger UI — auto-generated API docs at
/v1/docs(dev only) - Prometheus metrics —
/api-metricsendpoint (secret-protected in production) - Docker + Docker Compose — dev and prod environments
- Kubernetes — full manifests with HPA, ingress, RBAC, network policies
- Quick Start
- Project Structure
- Environment Variables
- API Endpoints
- Authentication Flow
- Service Integrations
- Socket.IO
- Testing
- Deployment
- Contributing
Prerequisites: Node.js 20+, MongoDB, Git
git clone https://github.com/ahmadalsharef994/nodejs-backend-starter-kit.git
cd nodejs-backend-starter-kit
npm install
cp .env.example .env
# Edit .env — only MONGODB_URL and JWT_SECRET are required to start
npm run devServer starts at http://localhost:3000.
- Health check:
GET /health - API docs:
GET /v1/docs(development only) - Metrics:
GET /api-metrics(requiresx-metrics-secretheader in production)
npm run dev # Development server with nodemon
npm start # Production server
npm test # Run Jest tests
npm run test:coverage # Tests with coverage report
npm run lint # ESLint
npm run lint:fix # ESLint auto-fix
npm run format # Prettier
npm run docker:dev # Docker dev environment
npm run docker:prod # Docker prod environmentsrc/
├── config/
│ ├── config.js # Joi-validated env config
│ ├── appLogger.js # Winston logger
│ ├── httpLogger.js # Morgan HTTP logger
│ ├── jwtStrategy.js # Passport JWT strategy
│ └── roles.js # Role definitions
├── controllers/ # Route handlers (thin layer)
├── middlewares/
│ ├── auth.js # JWT auth middleware (Passport)
│ ├── validate.js # Joi request validation
│ ├── error.js # Centralised error handling
│ └── rateLimiter.js # Auth + OTP rate limiters
├── models/
│ ├── user.model.js
│ ├── token.model.js
│ ├── otp.model.js
│ ├── notification.model.js
│ └── plugins/ # toJSON, paginate plugins
├── routes/v1/ # Versioned API routes
├── services/ # All business logic lives here
│ ├── auth.service.js
│ ├── token.service.js
│ ├── otp.service.js
│ ├── email.service.js
│ ├── sms.service.js
│ ├── storage.service.js
│ ├── payment.service.js
│ └── search.service.js
├── utils/
│ ├── ApiError.js # Custom error class
│ ├── catchAsync.js # Async error wrapper
│ └── pick.js # Object key picker
├── validations/ # Joi schemas
├── app.js # Express app setup
└── index.js # Entry point + Socket.IO
Copy .env.example to .env. Only the starred variables are required to boot:
# Server
NODE_ENV=development
PORT=3000
# Database ★
MONGODB_URL=mongodb://localhost:27017/starter-kit
# JWT ★
JWT_SECRET=your-secret-key-change-this
JWT_ACCESS_EXPIRATION_MINUTES=30
JWT_REFRESH_EXPIRATION_DAYS=30
JWT_RESET_PASSWORD_EXPIRATION_MINUTES=10
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES=10
# Email (optional — falls back to Ethereal Email in dev)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=your-email@gmail.com
SMTP_PASSWORD=your-app-password
EMAIL_FROM=noreply@yourapp.com
# Cloudinary (optional)
CLOUDINARY_CLOUD_NAME=
CLOUDINARY_API_KEY=
CLOUDINARY_API_SECRET=
# Razorpay (optional)
RAZORPAY_KEY_ID=
RAZORPAY_KEY_SECRET=
# Elasticsearch (optional)
ELASTIC_URL=http://localhost:9200
ELASTIC_USERNAME=elastic
ELASTIC_PASSWORD=
# Metrics endpoint protection (production)
METRICS_SECRET=your-metrics-secret
# Client URL (for email links)
CLIENTURL=http://localhost:3001| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/register |
❌ | Register a new user |
POST |
/login |
❌ | Login with email + password |
GET |
/profile |
✅ | Get current user profile |
POST |
/logout |
✅ | Logout (invalidates token) |
POST |
/forgot-password |
❌ | Send password reset OTP |
POST |
/reset-password |
❌ | Reset password with OTP |
POST |
/change-password |
✅ | Change password |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/ |
✅ Admin | List all users |
GET |
/:userId |
✅ | Get user by ID |
PATCH |
/:userId |
✅ | Update user |
DELETE |
/:userId |
✅ Admin | Delete user |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/health |
❌ | Health check |
GET |
/api-metrics |
Header | Prometheus metrics |
GET |
/v1/docs |
❌ (dev) | Swagger UI |
# 1. Register
POST /v1/auth/register
{
"fullName": "Jane Doe",
"email": "jane@example.com",
"phone": "1234567890",
"password": "Secure123",
"role": "user"
}
# 2. Login → get access token
POST /v1/auth/login
{
"email": "jane@example.com",
"password": "Secure123"
}
# Response: { user: {...}, token: { access: { token, expires }, refresh: { token, expires } } }
# 3. Use token in subsequent requests
GET /v1/auth/profile
Authorization: Bearer <access_token>
# 4. Forgot password
POST /v1/auth/forgot-password { "email": "jane@example.com" }
POST /v1/auth/reset-password { "email": "...", "otp": "123456", "newPassword": "..." }Tokens are JWTs signed with JWT_SECRET. Refresh tokens are stored in MongoDB and can be revoked. OTPs are generated with crypto.randomInt() and expire in 10 minutes.
All services run in mock mode when credentials are not set — the app boots without any external dependencies.
import emailService from './services/email.service.js';
await emailService.sendOtpEmail('user@example.com', '123456');
await emailService.sendVerificationEmail('user@example.com', token);
await emailService.sendWelcomeEmail('user@example.com', 'Jane');
await emailService.sendResetPasswordEmail('user@example.com', token);import storageService from './services/storage.service.js';
const result = await storageService.uploadImage('./avatar.jpg');import paymentService from './services/payment.service.js';
const order = await paymentService.createOrder(1000, 'INR');
const isValid = paymentService.verifyPaymentSignature(orderId, paymentId, signature);import searchService from './services/search.service.js';
await searchService.indexDocument('users', userData, userId);
const results = await searchService.fullTextSearch('users', 'jane doe', ['fullName', 'email']);Real-time events are configured in src/index.js.
| Event (client → server) | Description |
|---|---|
join_appointment |
Join a room by appointment ID |
send_message |
Send a chat message with optional attachments |
| Event (server → client) | Description |
|---|---|
receive_message |
Delivers a message to the recipient room |
import { io } from 'socket.io-client';
const socket = io('http://localhost:3000');
socket.emit('join_appointment', appointmentId);
socket.emit('send_message', { appointmentId, body: 'Hello!', senderId: userId });
socket.on('receive_message', (msg) => console.log(msg.body));npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage report
npm test -- auth.test.js # Run a specific fileTests use MongoDB Memory Server — no real database needed.
import request from 'supertest';
import app from '../../src/app.js';
import setupTestDB from '../utils/setupTestDB.js';
setupTestDB();
describe('POST /v1/auth/register', () => {
test('registers a new user', async () => {
const res = await request(app)
.post('/v1/auth/register')
.send({
fullName: 'Test User',
email: 'test@example.com',
phone: '1234567890',
password: 'Password123',
role: 'user',
})
.expect(201);
expect(res.body.user.email).toBe('test@example.com');
});
});npm run docker:dev # Dev with MongoDB included
npm run docker:prod # ProductionProduction image uses node:24-alpine with PM2 process manager.
Full manifests are in /k8s/ including HPA, ingress, RBAC, and network policies.
cd k8s && ./deploy.sh
kubectl get deployments,pods,services,hpa -l app=nodejs-backend
kubectl logs -l app=nodejs-backend -f
kubectl scale deployment nodejs-backend-deployment --replicas=3
./cleanup.shIncluded manifests: deployment.yaml, hpa.yaml (2–10 pods, CPU ≥ 70%), service.yaml, ingress.yaml (SSL via cert-manager), network-policy.yaml, rbac.yaml, monitoring.yaml (Prometheus ServiceMonitor).
See k8s/README.md for full instructions.
- Fork the repo
- Create a branch:
git checkout -b feature/my-feature - Commit:
git commit -m 'feat: add my feature' - Push and open a Pull Request
Run npm run lint and npm test before submitting.
MIT — see LICENSE for details.
Built with ❤️ for the developer community.