A production-ready Express.js backend template with authentication, email verification, file uploads, and auto-generated API documentation.
| Category | Features |
|---|---|
| Auth | JWT, refresh tokens, email verification, password reset, token blacklisting |
| Database | PostgreSQL/MySQL, Knex migrations, BaseModel with CRUD |
| SMTP-based email service with templates | |
| Uploads | Local or Cloudinary, group-based organization |
| API Docs | Auto-generated Swagger from Zod schemas |
| Security | Helmet, CORS, rate limiting, password hashing |
| Logging | Pino (console + file), request logging |
| Jobs | Cron scheduler for background tasks |
| Docker | Dockerfile + docker-compose ready |
# Clone and install
git clone <repo-url>
cd express-backend-template
pnpm install
# Configure
cp .env.example .env
# Edit .env with your settings
# Run
pnpm run devAccess:
src/
βββ config/ # Configuration (db, env, logger, swagger, upload)
βββ controllers/ # Route handlers
βββ database/ # Migrations and seeds
βββ middlewares/ # Auth, validation, rate limiting, uploads
βββ models/ # BaseModel + data models
βββ routes/ # API routes
βββ services/ # Business logic (Auth, Email, Token, Upload, User)
βββ utils/ # Helpers, errors, response formatters
βββ validators/ # Zod schemas
βββ cron/ # Scheduled jobs
βββ app.js # Express setup
βββ server.js # Entry point
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /auth/register |
No | Register + verification email |
| POST | /auth/login |
No | Login |
| POST | /auth/logout |
Yes | Logout + blacklist token |
| POST | /auth/refresh |
No | Refresh access token |
| GET | /auth/me |
Yes | Get profile |
| PATCH | /auth/me |
Yes | Update profile |
| POST | /auth/change-password |
Yes | Change password |
| GET | /auth/verify-email |
No | Verify email (token in query) |
| POST | /auth/resend-verification |
No | Resend verification email |
| POST | /auth/forgot-password |
No | Request password reset |
| POST | /auth/reset-password |
No | Reset password with token |
JWT_SECRET=your-secret-at-least-32-chars
JWT_EXPIRES_IN=7d
JWT_REFRESH_SECRET=your-refresh-secret
JWT_REFRESH_EXPIRES_IN=30dSMTP-based email service with built-in templates.
EMAIL_HOST=smtp.example.com
EMAIL_PORT=587
EMAIL_USER=your_email
EMAIL_PASSWORD=your_password
EMAIL_FROM=noreply@example.comimport { emailService } from './services/EmailService.js';
await emailService.send({ to, subject, html });
await emailService.sendVerificationEmail(to, name, token);
await emailService.sendPasswordResetEmail(to, name, token);Supports local storage or Cloudinary with group-based organization.
UPLOAD_STRATEGY=local
UPLOAD_LOCAL_PATH=uploads
# Or for Cloudinary
UPLOAD_STRATEGY=cloudinary
CLOUDINARY_CLOUD_NAME=xxx
CLOUDINARY_API_KEY=xxx
CLOUDINARY_API_SECRET=xxximport upload from './middlewares/upload.js';
import { upload as uploadFile } from './services/UploadService.js';
router.post('/image', upload('products').single('file'), async (req, res) => {
const result = await uploadFile(req.file, 'products');
// β uploads/products/123456-abc.jpg
res.json(result);
});Edit src/config/upload.js:
export const uploadGroups = {
products: { types: ALLOWED_TYPES.image, maxSize: 10 * 1024 * 1024 },
documents: { types: ALLOWED_TYPES.document, maxSize: 20 * 1024 * 1024 },
};DB_CLIENT=pg # or mysql2
DB_HOST=localhost
DB_PORT=5432
DB_NAME=app_db
DB_USER=postgres
DB_PASSWORD=passwordpnpm run migrate # Run migrations
pnpm run migrate:make <name> # Create migration
pnpm run migrate:rollback # Rollback
pnpm run seed # Run seeds
pnpm run db:reset # Reset databaseimport { BaseModel } from './models/BaseModel.js';
class ProductModel extends BaseModel {
constructor() {
super('products', {
timestamps: true,
softDeletes: true,
searchableFields: ['name', 'description'],
});
}
}
// Usage
await ProductModel.create({ name: 'Widget' });
await ProductModel.findAll({ page: 1, limit: 10, search: 'widget' });
await ProductModel.findById(id);
await ProductModel.update(id, { name: 'New Name' });
await ProductModel.delete(id);Auto-generated from Zod schemas. Access at /api/v1/docs.
Edit src/config/swagger.js:
import { postDoc, getDoc } from '../utils/routeDoc.js';
const autoRoutes = [
postDoc('/products', {
summary: 'Create product',
tags: ['Products'],
bodySchema: productSchemas.create,
auth: true,
}),
];Using Zod:
// src/validators/schemas.js
export const productSchemas = {
create: z.object({
name: z.string().min(1),
price: z.number().positive(),
}),
};
// In routes
import { validateBody } from './middlewares/validate.js';
router.post('/products', validateBody(productSchemas.create), handler);// src/cron/index.js
import { registerJob } from './index.js';
registerJob('daily-cleanup', '0 0 * * *', async () => {
// Runs daily at midnight
});# Development
docker-compose up -d
# Production build
docker build -t express-backend .
docker run -p 3000:3000 --env-file .env express-backend| Script | Description |
|---|---|
pnpm dev |
Development with hot reload |
pnpm start |
Production |
pnpm test |
Run tests |
pnpm lint |
ESLint |
pnpm format |
Prettier |
pnpm migrate |
Run migrations |
pnpm seed |
Run seeds |
- Helmet - Security headers
- CORS - Configurable origins
- Rate Limiting - Per route/user
- Password Hashing - bcrypt (configurable)
- JWT Blacklisting - Proper logout
- Input Validation - Zod schemas
| File | Description |
|---|---|
docs/EMAIL_TOKENS.md |
Email & token services |
docs/UPLOAD_SERVICE.md |
File upload guide |
docs/API_DOCUMENTATION.md |
Auto-docs setup |
README_DATABASE.md |
Database configuration |
- Model β
src/models/FeatureModel.js - Service β
src/services/FeatureService.js - Controller β
src/controllers/FeatureController.js - Routes β
src/routes/featureRoutes.js - Schema β
src/validators/schemas.js - Register β
src/routes/index.js - Docs β
src/config/swagger.js
MIT
Happy Coding! π