- Introduction to Express.js
- What is Express.js?
- Why Use Express.js?
- When to Use Express.js?
- Express.js vs Other Node Frameworks
- Install Node.js (If not installed)
- Installing Express.js
- First Express Server Example
- Basic Project Structure Example
- Setting Up the Development Environment & Project Structure Best Practices
- Node.js Environment Setup
- Initialize a New Express Project
- Project Structure Best Practices
- Creating a Basic Server with Organized Structure
- Environment Configuration
- Recommended npm Packages for Real-world Projects
- 3 Express.js Routing - Basics to Advanced
- What is Routing in Express?
- Basic Routing Syntax
- Basic Route Example
- Route Parameters
- Query Parameters
- Handling POST Requests with JSON Body
- Using express.Router()
- Route Chaining
- Route Wildcards
- Catch-all Route (404 Handler)
-
4 Middleware in Express.js - In Depth
- What is Middleware?
- Middleware Syntax
- Types of Middleware
- Application-level Middleware Example
- Route-specific Middleware Example
- Built-in Middleware
- Third-party Middleware Examples
- CORS
- Helmet (Security Headers)
- Router-level Middleware Example
- Error-handling Middleware
- Order of Middleware Matters
- Example: Complete Middleware Flow
-
5 Request and Response Handling in Express.js (In-Depth)
- Understanding the req (Request) Object
- Understanding the res (Response) Object
- Parsing Request Body
- Handling File Uploads
- Sending Files to Client
- Setting Response Headers
- Handling Different Content Types
- Response Status Codes Example
-
6: RESTful API Development with Express.js - Complete Guide
- What is a RESTful API?
- RESTful HTTP Methods Mapping
- Standard RESTful Endpoint Example for `users`
- Example Project Structure for REST API
- API Example Without Database (In-memory Array)
- API Testing Examples
- Best Practices for REST API in Express
-
7: Authentication and Authorization in Express.js (Complete Guide)
- Difference Between Authentication and Authorization
- Common Authentication Methods
- JWT (JSON Web Token) Authentication
- Role-Based Access Control (RBAC)
- OAuth and Social Login (Concept)
- API Security Best Practices
-
8: Database Integration with Express.js (MongoDB & SQL)
- Database Options with Express
- MongoDB Integration using **Mongoose (ODM)**
- SQL Integration using **Sequelize (ORM)**
- Database Schema Design Tips
- Transactions Handling
- Data Validation and Sanitization
- Goals of Advanced Routing
- Route Versioning (Recommended for Public APIs)
- Nested Routes (Resource Relationships)
- Parameter Middleware
- Error Handling in Routes
- Centralized Error Handler
- Clean RESTful API Design Patterns
- Example of Well-Designed API
- Organizing Routes for Scalability
-
9: Advanced Routing in Express.js (Clean Patterns, Versioning, Error Handling)
- Goals of Advanced Routing
- Route Versioning (Recommended for Public APIs)
- Nested Routes (Resource Relationships)
- Parameter Middleware
- Error Handling in Routes
- Centralized Error Handler
- Clean RESTful API Design Patterns
- Example of Well-Designed API
- Organizing Routes for Scalability
-
10: Data Validation and Error Handling in Express.js (with `express-validator`)
- Why Input Validation is Critical
- Install express-validator
- Basic Validation Example
- Common Validators
- Sanitization Example
- Abstract Error Handler (Reusable Pattern)
- Complex Validation Example
- Global Error Handling (Server-level)
- Response Format Suggestion (Best Practice)
-
11: File Uploads and File Handling in Express.js (Multer, Local & Cloud Storage)
- File Upload Handling in Express
- Install Multer
- Basic File Upload Example
- Upload Multiple Files
- Accepting Specific File Types (Example: Images Only)
- Serving Uploaded Files
- File Validation and Security
- File Upload with Cloud Storage (Example: AWS S3)
- Other Cloud Options
- Example Folder Structure for Uploads
-
12: Performance, Security, and Rate Limiting in Express.js
- Core Focus Areas
- 1\. HTTP Security with Helmet
- 2\. Enable CORS (Cross-Origin Resource Sharing)
- 3\. Rate Limiting (DDoS and Abuse Prevention)
- 4\. Compression for Faster Responses
- 5\. Prevent HTTP Parameter Pollution
- 6\. Sanitize Input to Prevent NoSQL Injection and XSS
- 7\. Handling Caching
- 8\. Prevent Brute Force on Auth Routes
- 9\. Secure Environment Variables
- 10\. Best Practice Summary
-
13: Testing Express.js Applications (Unit, Integration, End-to-End)
- Why Testing is Critical
- Testing Types
- Recommended Stack
- Install Testing Libraries
- Configure Jest
- Basic Unit Test Example
- Integration Testing with Express + Supertest
- Testing Routes with CRUD Example
- Mocking Database (MongoDB Example)
- Testing Error Responses
- Running Tests
- Test Folder Structure (Suggested)
- Coverage Reporting
-
14: Deployment and Production for Express.js (PM2, NGINX, Docker, CI/CD)
- 1\. Preparing for Production
- 2\. Process Management with PM2
- 3\. Reverse Proxy with NGINX
- 4\. HTTPS with Certbot (Let's Encrypt)
- 5\. Dockerize Express App
- 6\. Docker Compose Example
- 7\. Environment Variables (.env)
- 8\. CI/CD Basics Example (GitHub Actions)
- 9\. Deployment Checklist
-
15: Real-time Communication in Express.js with Socket.IO (WebSockets)
- Why WebSockets?
- Install Socket.IO
- Server-side Setup
- Client-side Example (HTML + JavaScript)
- Namespaces (For Grouping Different Logics)
- Rooms (For Private Groups)
- Example Room Chat Flow
- Production Considerations
-
16: Express.js Best Practices, Clean Architecture, and Scalable Project Structure
- Goals
- Recommended Folder Structure
- Example File Responsibilities
- Controller Example
- Service Example (Business Logic)
- Central Error Handler
- Async Error Wrapper (DRY Pattern)
- Separate Route File Example
- Config Management Example
- Key Best Practices
- Deployment-Ready Checklist
- What is Express.js?
- Why Use Express.js?
- When to Use Express.js?
- Express.js vs Other Node Frameworks
- Install Node.js (If not installed)
- Installing Express.js
- First Express Server Example
- Basic Project Structure Example
- Node.js Environment Setup
- Initialize a New Express Project
- Project Structure Best Practices
- Creating a Basic Server with Organized Structure
- Environment Configuration
- Recommended npm Packages for Real-world Projects
Express.js is a minimal and flexible Node.js web application framework that provides a robust set of features to develop web and mobile applications. It is widely used to build REST APIs, web servers, and backend services.
Express is often described as “Fast, unopinionated, and minimalist” because it doesn't enforce strict design patterns or structure. You are free to structure your app the way you want.
-
Minimalistic but powerful: Provides only the core web server functionalities, but can be extended via middleware.
-
Easy to Learn: Simple API for routing and handling HTTP requests.
-
Massive Ecosystem: Supports thousands of NPM packages for authentication, validation, logging, etc.
-
Flexible Architecture: You can design it as monolithic, modular, or microservices.
-
Battle-tested: Used by companies like IBM, Uber, Accenture, and more.
-
RESTful API servers.
-
Backend for web apps or SPAs (with frontend frameworks like React, Vue, Angular).
-
Microservices.
-
Prototyping and MVPs.
-
Handling server-side logic in full-stack apps.
| Framework | Type | Features |
|---|---|---|
| Express.js | Minimal | Unopinionated, middleware-driven |
| Koa | Minimal | Modern, lightweight, async-first |
| Fastify | Minimal | High performance, schema-based, faster |
| NestJS | Full-fledged | Opinionated, TypeScript-based, modular |
| Hapi | Config-driven | Built-in validation, configuration rich |
- Download from: https://nodejs.org/
Check version:
node -v
npm -v- Create a new directory:
mkdir express-demo
cd express-demo- Initialize the project:
npm init -y- Install Express:
npm install expressCreate a file app.js:
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello, Express.js!');
});
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});Run the server:
node app.jsCheck in browser:
http://localhost:3000
Output:
Hello, Express.js!
express-demo/
├── app.js
├── package.json
├── routes/
│ └── user.js
├── controllers/
│ └── userController.js
├── models/
│ └── userModel.js
├── middlewares/
│ └── auth.js
├── public/
│ └── images/
├── views/ (optional for SSR)
└── README.md- Install Node.js
-
Download and install from https://nodejs.org
-
Install the LTS version for stability.
- Verify Installation
node -v
npm -v- Create a New Directory
mkdir express-app
cd express-app- Initialize npm
npm init -y- Install Express
npm install express- Install Developer Tools (Optional)
nodemonfor auto-restart on file changes:
npm install --save-dev nodemon- Update
package.jsonfor development:
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
}express-app/
├── app.js
├── package.json
├── routes/
├── public/
└── README.mdexpress-app/
├── app.js
├── package.json
├── /routes
│ └── userRoutes.js
├── /controllers
│ └── userController.js
├── /models
│ └── userModel.js
├── /middlewares
│ └── authMiddleware.js
├── /services
│ └── emailService.js
├── /config
│ └── dbConfig.js
├── /utils
│ └── errorHandler.js
├── /public
│ └── static assets (images, css)
├── /logs
│ └── log files
└── README.mdconst express = require('express');
const app = express();
const userRoutes = require('./routes/userRoutes');
app.use(express.json()); // to parse JSON body
app.use('/api/users', userRoutes);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
router.get('/', userController.getAllUsers);
router.post('/', userController.createUser);
module.exports = router;exports.getAllUsers = (req, res) => {
res.json({ message: 'List of users' });
};
exports.createUser = (req, res) => {
const { name } = req.body;
res.json({ message: `User ${name} created` });
};- Install
dotenvfor environment variable management:
npm install dotenv- Create
.envfile:
PORT=3000
DB_URL=mongodb://localhost:27017/expressdb
JWT_SECRET=yourSecretKey- Load in
app.js:
require('dotenv').config();| Package | Purpose |
|---|---|
| express | Core framework |
| dotenv | Environment variable management |
| morgan | HTTP request logger |
| cors | Cross-origin resource sharing |
| helmet | Security headers |
| express-validator | Validation & sanitization |
| jsonwebtoken | JWT authentication |
| mongoose/sequelize/knex | Database integration |
Routing defines how an application responds to client requests to a particular endpoint (URL) with a specific HTTP method (GET, POST, PUT, DELETE, etc.).
app.METHOD(PATH, HANDLER)-
METHOD: HTTP method (
get,post,put,delete, etc.) -
PATH: Endpoint path
-
HANDLER: Callback function
(req, res) => {}
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Home Page');
});
app.post('/submit', (req, res) => {
res.send('Data submitted');
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});- Used for dynamic routes.
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.send(`User ID: ${userId}`);
});Test URL:
http://localhost:3000/users/123Response:
User ID: 123app.get('/search', (req, res) => {
const keyword = req.query.q;
res.send(`Search result for: ${keyword}`);
});Test URL:
http://localhost:3000/search?q=expressResponse:
Search result for: express- Install body parser (built-in from Express 4.16+):
app.use(express.json());Example:
app.post('/users', (req, res) => {
const { name, email } = req.body;
res.json({ message: `User ${name} with email ${email} created` });
});Test with Postman:
-
Method: POST
-
URL:
http://localhost:3000/users -
Body:
{"name": "John", "email": "john@example.com"}
- To modularize and split routes.
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
router.get('/', userController.getAllUsers);
router.get('/:id', userController.getUserById);
router.post('/', userController.createUser);
router.put('/:id', userController.updateUser);
router.delete('/:id', userController.deleteUser);
module.exports = router;exports.getAllUsers = (req, res) => {
res.send('Get all users');
};
exports.getUserById = (req, res) => {
res.send(`Get user with ID ${req.params.id}`);
};
exports.createUser = (req, res) => {
res.send('Create user');
};
exports.updateUser = (req, res) => {
res.send(`Update user with ID ${req.params.id}`);
};
exports.deleteUser = (req, res) => {
res.send(`Delete user with ID ${req.params.id}`);
};const userRoutes = require('./routes/userRoutes');
app.use('/api/users', userRoutes);Test URLs:
-
GET /api/users -
POST /api/users -
GET /api/users/:id -
PUT /api/users/:id -
DELETE /api/users/:id
For cleaner route definitions with the same path.
router
.route('/')
.get(userController.getAllUsers)
.post(userController.createUser);
router
.route('/:id')
.get(userController.getUserById)
.put(userController.updateUser)
.delete(userController.deleteUser);- Matches pattern-based routes.
app.get('/ab*cd', (req, res) => {
res.send('Matched route: /ab*cd');
});Test URL:
/abcd
/ab123cd
/abXYZcdAll match.
app.use((req, res) => {
res.status(404).send('404 - Not Found');
});-
Middleware are functions that execute during the request-response cycle before sending a response to the client.
-
Middleware can:
-
Execute any code
-
Modify
reqandresobjects -
End the request-response cycle
-
Pass control to the next middleware using
next()
-
function middlewareName(req, res, next) {
// Code to execute
next(); // Pass control to next middleware
}Register middleware:
app.use(middlewareName);-
Application-level middleware
Applied to the entire app usingapp.use()or route-specific. -
Router-level middleware
Applied to anexpress.Router()instance. -
Built-in middleware
Provided by Express (express.json(),express.static(), etc.). -
Third-party middleware
Installed via NPM (morgan,cors,helmet, etc.). -
Error-handling middleware
Specifically for handling errors.
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});This logs every incoming request.
const checkAuth = (req, res, next) => {
const authorized = true;
if (authorized) {
next();
} else {
res.status(401).send('Unauthorized');
}
};
app.get('/secure', checkAuth, (req, res) => {
res.send('You have access');
});| Middleware | Purpose |
|---|---|
express.json() |
Parse JSON bodies |
express.urlencoded() |
Parse URL-encoded data |
express.static() |
Serve static files |
app.use(express.json());
app.use(express.static('public'));npm install morganconst morgan = require('morgan');
app.use(morgan('dev'));npm install corsconst cors = require('cors');
app.use(cors());npm install helmetconst helmet = require('helmet');
app.use(helmet());const express = require('express');
const router = express.Router();
router.use((req, res, next) => {
console.log('Router-specific middleware triggered');
next();
});
router.get('/', (req, res) => {
res.send('Users list');
});
module.exports = router;const userRoutes = require('./routes/userRoutes');
app.use('/api/users', userRoutes);- Express recognizes it by having 4 parameters:
(err, req, res, next)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});Throw an error manually:
app.get('/error', (req, res) => {
throw new Error('Manual error');
});- Middleware is executed in the order they are defined.
Example:
app.use(middlewareA);
app.use('/route', middlewareB);
app.get('/route', handler);Execution order:
middlewareA → middlewareB → handlerconst express = require('express');
const app = express();
const morgan = require('morgan');
app.use(morgan('dev')); // Logging middleware
app.use(express.json()); // Body parser
// Custom middleware
app.use((req, res, next) => {
console.log('Custom middleware running');
next();
});
// Sample route
app.get('/', (req, res) => {
res.send('Home Route');
});
// Error middleware
app.use((err, req, res, next) => {
console.error(err);
res.status(500).send('Error occurred');
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});The req object represents the HTTP request and has properties for the request query string, parameters, body, headers, and more.
| Property | Description |
|---|---|
req.params |
Route parameters /user/:id |
req.query |
Query string parameters ?search=abc |
req.body |
Request body (POST, PUT) |
req.headers |
Request headers |
req.method |
HTTP method |
req.url |
Original URL |
app.get('/user/:id', (req, res) => {
console.log('Params:', req.params);
console.log('Query:', req.query);
console.log('Headers:', req.headers);
res.send('Check console');
});Test URL:
http://localhost:3000/user/123?name=JohnOutput:
Params: { id: '123' }
Query: { name: 'John' }The res object is used to send a response back to the client.
| Method | Description |
|---|---|
res.send() |
Send text, JSON, or buffer |
res.json() |
Send JSON response |
res.status() |
Set HTTP status code |
res.redirect() |
Redirect to another URL |
res.download() |
Send a file for download |
res.sendFile() |
Send a static file |
res.set() |
Set response headers |
app.get('/json', (req, res) => {
res.status(200).json({ message: 'Hello JSON' });
});From Express 4.16+, express.json() and express.urlencoded() are built-in middlewares for parsing.
app.use(express.json());Test POST:
app.post('/data', (req, res) => {
console.log(req.body);
res.json({ received: req.body });
});Send JSON:
{
"name": "John",
"email": "john@example.com"
}app.use(express.urlencoded({ extended: true }));Test POST (from HTML form or Postman x-www-form-urlencoded):
app.post('/form', (req, res) => {
console.log(req.body);
res.send('Form received');
});Install multer:
npm install multerconst express = require('express');
const multer = require('multer');
const app = express();
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/'); // Directory to save files
},
filename: function (req, file, cb) {
cb(null, Date.now() + '-' + file.originalname);
}
});
const upload = multer({ storage: storage });
app.post('/upload', upload.single('profile'), (req, res) => {
res.send(`File uploaded: ${req.file.filename}`);
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});-
Create folder
uploads/in your project root. -
Use key
profilein Postman or HTML form.
app.get('/download', (req, res) => {
res.download('./uploads/sample.pdf');
});app.get('/file', (req, res) => {
res.sendFile(__dirname + '/uploads/sample.pdf');
});app.get('/set-header', (req, res) => {
res.set('X-Custom-Header', 'ExpressTutorial');
res.send('Header Set');
});Example to handle plain text:
app.get('/text', (req, res) => {
res.type('text').send('Plain text response');
});app.get('/unauthorized', (req, res) => {
res.status(401).json({ error: 'Unauthorized access' });
});REST (Representational State Transfer) is an architectural style for designing APIs based on HTTP methods and stateless communication. RESTful APIs expose resources through endpoints, supporting CRUD operations.
| HTTP Method | Operation | Description |
|---|---|---|
| GET | Read | Fetch data |
| POST | Create | Create new data |
| PUT | Update | Update/replace |
| PATCH | Partial Update | Partially update data |
| DELETE | Delete | Remove data |
| Method | Endpoint | Action |
|---|---|---|
| GET | /api/users |
Get all users |
| GET | /api/users/:id |
Get user by ID |
| POST | /api/users |
Create a user |
| PUT | /api/users/:id |
Update entire user |
| PATCH | /api/users/:id |
Partially update user |
| DELETE | /api/users/:id |
Delete user |
express-api/
├── app.js
├── routes/
│ └── userRoutes.js
├── controllers/
│ └── userController.js
├── models/
│ └── userModel.js (optional for DB)
└── package.jsonlet users = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
];
exports.getAllUsers = (req, res) => {
res.json(users);
};
exports.getUserById = (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
};
exports.createUser = (req, res) => {
const { name } = req.body;
const newUser = { id: Date.now(), name };
users.push(newUser);
res.status(201).json(newUser);
};
exports.updateUser = (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
user.name = req.body.name;
res.json(user);
};
exports.deleteUser = (req, res) => {
users = users.filter(u => u.id !== parseInt(req.params.id));
res.json({ message: 'User deleted' });
};const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
router.route('/')
.get(userController.getAllUsers)
.post(userController.createUser);
router.route('/:id')
.get(userController.getUserById)
.put(userController.updateUser)
.delete(userController.deleteUser);
module.exports = router;const express = require('express');
const app = express();
const userRoutes = require('./routes/userRoutes');
app.use(express.json());
app.use('/api/users', userRoutes);
app.use((req, res) => {
res.status(404).json({ error: 'Not Found' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});GET http://localhost:3000/api/usersGET http://localhost:3000/api/users/1POST http://localhost:3000/api/users
Body:
{
"name": "Alice"
}PUT http://localhost:3000/api/users/1
Body:
{
"name": "Updated Name"
}DELETE http://localhost:3000/api/users/1-
Use nouns, not verbs, in endpoints (
/usersnot/getUsers). -
Follow HTTP status codes correctly:
-
200 OKfor success -
201 Createdafter POST -
404 Not Foundif resource doesn't exist -
400 Bad Requestfor invalid input -
500 Internal Server Errorfor server issues
-
-
Handle errors gracefully with meaningful messages.
-
Validate incoming data (covered in later sections).
-
Use consistent response structures.
-
Modularize routes and controllers for scalability.
| Aspect | Authentication | Authorization |
|---|---|---|
| Purpose | Verifies who you are | Verifies what you can access |
| Process | Login, credentials check | Permission check after authentication |
| Result | User identity confirmed | Access to resources is granted/denied |
-
Session-based Authentication: Cookies + Server session.
-
Token-based Authentication (JWT): Stateless, scalable.
-
OAuth: Third-party logins (Google, GitHub, etc.).
npm install jsonwebtoken bcryptjs-
User logs in with credentials.
-
Server verifies and issues a JWT token.
-
Client stores token (typically in localStorage or cookies).
-
Client sends token with each request in the header.
-
Server verifies the token to allow access.
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const users = [
{ id: 1, username: 'admin', password: bcrypt.hashSync('admin123', 8), role: 'admin' }
];
const JWT_SECRET = 'your_jwt_secret_key';
exports.login = (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username);
if (!user) {
return res.status(401).json({ error: 'Invalid username or password' });
}
const isPasswordValid = bcrypt.compareSync(password, user.password);
if (!isPasswordValid) {
return res.status(401).json({ error: 'Invalid username or password' });
}
const token = jwt.sign(
{ id: user.id, username: user.username, role: user.role },
JWT_SECRET,
{ expiresIn: '1h' }
);
res.json({ token });
};
exports.profile = (req, res) => {
res.json({ user: req.user });
};const jwt = require('jsonwebtoken');
const JWT_SECRET = 'your_jwt_secret_key';
module.exports = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, JWT_SECRET);
req.user = decoded; // Attach user data to request
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
};const express = require('express');
const router = express.Router();
const authController = require('../controllers/authController');
const authMiddleware = require('../middlewares/authMiddleware');
router.post('/login', authController.login);
router.get('/profile', authMiddleware, authController.profile);
module.exports = router;const express = require('express');
const app = express();
const authRoutes = require('./routes/authRoutes');
app.use(express.json());
app.use('/api/auth', authRoutes);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});- Login
POST http://localhost:3000/api/auth/login
Body:
{
"username": "admin",
"password": "admin123"
}Response:
{
"token": "eyJhbGciOiJIUzI1NiIs..."
}- Access Profile (Protected Route)
GET http://localhost:3000/api/auth/profile
Headers:
Authorization: Bearer <your_token>module.exports = function(roles = []) {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Access forbidden' });
}
next();
};
};const roleMiddleware = require('./middlewares/roleMiddleware');
router.get('/admin-data', authMiddleware, roleMiddleware(['admin']), (req, res) => {
res.send('Admin-only data');
});-
Use packages like
passportandpassport-google-oauth20. -
Flow:
-
Redirect user to Google login.
-
Google sends back a token.
-
Server verifies token and creates/returns user info.
-
This is an advanced topic covered in-depth in authentication frameworks or the Passport section.
-
Store secrets like JWT secret in
.envfile. -
Always use HTTPS in production.
-
Set proper token expiration (
1h,15m, etc.). -
Secure token storage (HTTP-only cookies recommended for web apps).
-
Use rate limiting and brute-force protection.
-
Use security headers (
helmetmiddleware).
-
NoSQL: MongoDB (document-based)
-
SQL: MySQL, PostgreSQL, SQLite (relational)
Express works with both types using popular Node.js libraries.
npm install mongooseconst mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/expressdb', {
useNewUrlParser: true,
useUnifiedTopology: true
}).then(() => {
console.log('MongoDB connected');
}).catch(err => {
console.error('MongoDB connection error:', err);
});const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, unique: true, required: true },
createdAt: { type: Date, default: Date.now }
});
const User = mongoose.model('User', userSchema);
module.exports = User;const User = require('../models/User');
exports.createUser = async (req, res) => {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (err) {
res.status(400).json({ error: err.message });
}
};exports.getAllUsers = async (req, res) => {
const users = await User.find();
res.json(users);
};exports.getUserById = async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
};exports.updateUser = async (req, res) => {
const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true });
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
};exports.deleteUser = async (req, res) => {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) return res.status(404).json({ error: 'User not found' });
res.json({ message: 'User deleted' });
};npm install sequelize mysql2Or for PostgreSQL:
npm install sequelize pg pg-hstoreconst { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('expressdb', 'root', 'password', {
host: 'localhost',
dialect: 'mysql' // Or 'postgres'
});
sequelize.authenticate()
.then(() => console.log('SQL Database connected'))
.catch(err => console.error('Connection error:', err));const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false
}
});
sequelize.sync();
module.exports = User;exports.createUser = async (req, res) => {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (err) {
res.status(400).json({ error: err.message });
}
};exports.getAllUsers = async (req, res) => {
const users = await User.findAll();
res.json(users);
};exports.getUserById = async (req, res) => {
const user = await User.findByPk(req.params.id);
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
};exports.updateUser = async (req, res) => {
const user = await User.findByPk(req.params.id);
if (!user) return res.status(404).json({ error: 'User not found' });
await user.update(req.body);
res.json(user);
};exports.deleteUser = async (req, res) => {
const user = await User.findByPk(req.params.id);
if (!user) return res.status(404).json({ error: 'User not found' });
await user.destroy();
res.json({ message: 'User deleted' });
};-
Keep data normalized in SQL; denormalized (if needed) in MongoDB.
-
Add indexes for frequently queried fields (
email,username). -
Use proper data types:
String,Number,Boolean,Date. -
Handle validations at both DB schema and API validation layer.
const session = await mongoose.startSession();
session.startTransaction();
try {
// perform multiple operations
await session.commitTransaction();
} catch (error) {
await session.abortTransaction();
}
session.endSession();const t = await sequelize.transaction();
try {
await User.create({ name: 'Alice' }, { transaction: t });
await t.commit();
} catch (error) {
await t.rollback();
}-
Schema-level validation using Mongoose or Sequelize.
-
API-level validation using
express-validator(covered in next sections).
-
Design scalable and clean API endpoints.
-
Support versioning for API lifecycle management.
-
Handle nested resources properly.
-
Manage errors systematically.
app.use('/api/v1/users', require('./routes/userRoutes'));
app.use('/api/v2/users', require('./routes/userRoutesV2'));-
GET /api/v1/users— old version -
GET /api/v2/users— upgraded version
Endpoints:
| Method | Endpoint | Action |
|---|---|---|
| GET | /api/users/:userId/posts |
Get posts for a user |
| POST | /api/users/:userId/posts |
Create post for a user |
| GET | /api/users/:userId/posts/:postId |
Get specific post |
routes/postRoutes.js
const express = require('express');
const router = express.Router({ mergeParams: true });
const postController = require('../controllers/postController');
router
.route('/')
.get(postController.getPosts)
.post(postController.createPost);
router
.route('/:postId')
.get(postController.getPostById);
module.exports = router;routes/userRoutes.js
const express = require('express');
const router = express.Router();
const postRoutes = require('./postRoutes');
const userController = require('../controllers/userController');
router.route('/').get(userController.getAllUsers);
router.use('/:userId/posts', postRoutes);
module.exports = router;Automatically handle route parameters.
router.param('userId', (req, res, next, id) => {
console.log(`UserID Param detected: ${id}`);
req.userId = id;
next();
});Applies to any route containing :userId.
const asyncHandler = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};Usage in routes:
router.get('/', asyncHandler(async (req, res) => {
const users = await User.find();
res.json(users);
}));app.use((err, req, res, next) => {
console.error(err.stack);
const status = err.status || 500;
res.status(status).json({
error: {
message: err.message || 'Internal Server Error'
}
});
});-
Use plural nouns for resources (
/users,/products). -
Nested resources for relations (
/users/:userId/posts). -
Avoid verbs in URL (
GET /usersnot/getUsers). -
Status codes must reflect API response correctly:
-
200 OK -
201 Created -
204 No Contentfor delete -
400/404/500as applicable
-
-
Version APIs (
/api/v1/).
GET /api/v1/users
POST /api/v1/users
GET /api/v1/users/:id
PUT /api/v1/users/:id
DELETE /api/v1/users/:id
GET /api/v1/users/:userId/posts
POST /api/v1/users/:userId/posts
GET /api/v1/users/:userId/posts/:postIdExample:
/routes
└── v1
├── userRoutes.js
└── postRoutes.js
└── v2
└── userRoutes.js (new version)In app.js:
app.use('/api/v1/users', require('./routes/v1/userRoutes'));
app.use('/api/v1/users/:userId/posts', require('./routes/v1/postRoutes'));
app.use('/api/v2/users', require('./routes/v2/userRoutes'));-
Prevent malformed or malicious data from entering the system.
-
Improve API reliability and security.
-
Return clear error messages to clients.
npm install express-validatorconst express = require('express');
const { body, validationResult } = require('express-validator');
const router = express.Router();
const userController = require('../controllers/userController');
router.post(
'/',
[
body('name').notEmpty().withMessage('Name is required'),
body('email').isEmail().withMessage('Valid email is required')
],
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
userController.createUser(req, res);
}
);
module.exports = router;POST /api/users
Body:
{
"name": "",
"email": "invalidemail"
}Response:
{
"errors": [
{ "msg": "Name is required", "param": "name" },
{ "msg": "Valid email is required", "param": "email" }
]
}| Validator | Description |
| | |
| body('field').notEmpty() | Required field |
| body('email').isEmail() | Must be valid email |
| body('age').isInt({ min: 0 }) | Integer, optionally with range |
| body('url').isURL() | Valid URL |
| body('password').isLength({ min: 6 }) | Password length validation |
body('email').normalizeEmail(),
body('name').trim().escape()-
trim(): Remove extra spaces. -
escape(): Prevent HTML injection. -
normalizeEmail(): Normalize email format.
const { validationResult } = require('express-validator');
module.exports = (req, res, next) => {
const errors = validationResult(req);
if (errors.isEmpty()) return next();
return res.status(400).json({
errors: errors.array().map(err => ({
field: err.param,
message: err.msg
}))
});
};const validate = require('../middlewares/validate');
router.post(
'/',
[
body('name').notEmpty().withMessage('Name is required'),
body('email').isEmail().withMessage('Valid email is required')
],
validate,
userController.createUser
);router.post(
'/register',
[
body('username').isAlphanumeric().withMessage('Username must be alphanumeric'),
body('password')
.isLength({ min: 6 })
.withMessage('Password must be at least 6 characters')
.matches(/[A-Z]/).withMessage('Password must contain an uppercase letter')
.matches(/[0-9]/).withMessage('Password must contain a number')
],
validate,
authController.register
);app.use((err, req, res, next) => {
console.error(err);
res.status(err.status || 500).json({
error: {
message: err.message || 'Internal Server Error'
}
});
});{
"status": "error",
"errors": [
{ "field": "email", "message": "Valid email is required" }
]
}npm install multerconst express = require('express');
const multer = require('multer');
const app = express();
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'uploads/'); // Save files in uploads/ folder
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, uniqueSuffix + '-' + file.originalname);
}
});
const upload = multer({ storage: storage });
app.post('/upload', upload.single('file'), (req, res) => {
res.json({ filename: req.file.filename, path: req.file.path });
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});uploads/folder should exist in the project root.
app.post('/upload-multiple', upload.array('files', 5), (req, res) => {
res.json({ files: req.files });
});const upload = multer({
storage: storage,
fileFilter: function (req, file, cb) {
const allowedTypes = /jpeg|jpg|png|gif/;
const isValidExt = allowedTypes.test(file.originalname.toLowerCase());
const isValidMime = allowedTypes.test(file.mimetype);
if (isValidExt && isValidMime) {
cb(null, true);
} else {
cb(new Error('Only images are allowed'));
}
},
limits: { fileSize: 2 * 1024 * 1024 } // 2MB limit
});app.use('/uploads', express.static('uploads'));Access file:
http://localhost:3000/uploads/filename.jpg-
Check file types (
fileFilter). -
Set size limits (
limits). -
Store files outside
public/folder to restrict direct access if needed. -
Rename files uniquely to avoid collisions.
-
Sanitize file names.
npm install aws-sdk multer-s3const AWS = require('aws-sdk');
const multerS3 = require('multer-s3');
AWS.config.update({
accessKeyId: 'YOUR_AWS_KEY',
secretAccessKey: 'YOUR_AWS_SECRET',
region: 'YOUR_AWS_REGION'
});
const s3 = new AWS.S3();
const upload = multer({
storage: multerS3({
s3: s3,
bucket: 'your-bucket-name',
acl: 'public-read',
metadata: (req, file, cb) => {
cb(null, { fieldName: file.fieldname });
},
key: (req, file, cb) => {
cb(null, Date.now().toString() + '-' + file.originalname);
}
})
});app.post('/upload', upload.single('file'), (req, res) => {
res.json({ fileUrl: req.file.location });
});-
Cloudinary: For images and videos (with built-in CDN).
-
Firebase Storage: For serverless environments.
-
Azure Blob Storage / Google Cloud Storage: Enterprise-grade solutions.
/uploads
├── user-profiles/
├── documents/
├── product-images/Organize uploads by type for easy maintenance.
-
Security against attacks (XSS, CSRF, HTTP headers).
-
Performance optimization (compression, caching).
-
Rate limiting to prevent abuse.
npm install helmetconst helmet = require('helmet');
app.use(helmet());-
Sets HTTP headers like:
-
Content-Security-Policy -
X-Frame-Options -
Strict-Transport-Security -
X-Content-Type-Options -
Prevents clickjacking, XSS, MIME sniffing.
-
npm install corsconst cors = require('cors');
app.use(cors());app.use(cors({
origin: ['https://yourfrontend.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true
}));npm install express-rate-limitconst rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per window
message: 'Too many requests from this IP, please try again later.'
});
app.use('/api/', apiLimiter);npm install compressionconst compression = require('compression');
app.use(compression());-
Reduces response body size using Gzip or Brotli.
-
Boosts load speed, especially on slow networks.
npm install hppconst hpp = require('hpp');
app.use(hpp());- Prevents HTTP parameter pollution attacks (
/search?sort=asc&sort=desc).
npm install express-mongo-sanitizeconst mongoSanitize = require('express-mongo-sanitize');
app.use(mongoSanitize());npm install xss-cleanconst xss = require('xss-clean');
app.use(xss());- Use
Cache-Controlheaders for static files or API caching.
app.use('/public', express.static('public', {
maxAge: '7d', // Cache static files for 7 days
}));Apply tighter rate limits on sensitive routes:
const authLimiter = rateLimit({
windowMs: 10 * 60 * 1000,
max: 5, // Max 5 attempts in 10 minutes
message: 'Too many login attempts, try again later.'
});
app.use('/api/auth/login', authLimiter);- Use
.envto store secrets.
npm install dotenvrequire('dotenv').config();.env
DB_PASSWORD=yourpassword
JWT_SECRET=yourjwtsecret-
✅ Use Helmet for HTTP header security.
-
✅ Enable CORS properly.
-
✅ Use express-rate-limit to throttle abusive requests.
-
✅ Apply compression for performance.
-
✅ Sanitize inputs using xss-clean, mongo-sanitize, and hpp.
-
✅ Use .env for sensitive configurations.
-
✅ Serve static assets with caching when needed.
-
Ensure reliability.
-
Prevent regressions.
-
Automate quality checks in CI/CD pipelines.
| Type | Focus | Tools |
|---|---|---|
| Unit Testing | Single functions/modules | Jest, Mocha |
| Integration | API endpoints, DB, services | Supertest + Jest |
| E2E Testing | Full app workflow | Cypress, Playwright |
-
Jest — Test framework and runner.
-
Supertest — HTTP assertions for Express.
-
Mock Libraries — e.g., jest-mock, mockingoose (MongoDB).
npm install --save-dev jest supertestAdd in package.json:
"scripts": {
"test": "jest"
}Or create jest.config.js:
module.exports = {
testEnvironment: 'node'
};utils/math.js
function add(a, b) {
return a + b;
}
module.exports = { add };tests/math.test.js
const { add } = require('../utils/math');
test('adds 1 + 2 equals 3', () => {
expect(add(1, 2)).toBe(3);
});Run:
npm testapp.js
const express = require('express');
const app = express();
app.use(express.json());
app.get('/api/ping', (req, res) => {
res.json({ message: 'pong' });
});
module.exports = app;server.js
const app = require('./app');
app.listen(3000, () => console.log('Server running'));tests/app.test.js
const request = require('supertest');
const app = require('../app');
describe('Ping Route', () => {
it('should respond with pong', async () => {
const res = await request(app).get('/api/ping');
expect(res.statusCode).toEqual(200);
expect(res.body).toHaveProperty('message', 'pong');
});
});Test POST + GET User
describe('User API', () => {
it('should create a user', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'John', email: 'john@example.com' });
expect(res.statusCode).toBe(201);
expect(res.body).toHaveProperty('name', 'John');
});
it('should fetch users', async () => {
const res = await request(app).get('/api/users');
expect(res.statusCode).toBe(200);
expect(Array.isArray(res.body)).toBeTruthy();
});
});npm install --save-dev mockingooseconst mockingoose = require('mockingoose');
const User = require('../models/User');
it('should mock User.find()', async () => {
mockingoose(User).toReturn([{ name: 'Test' }], 'find');
const res = await request(app).get('/api/users');
expect(res.body[0].name).toBe('Test');
});it('should return 400 if invalid input', async () => {
const res = await request(app)
.post('/api/users')
.send({ email: 'not-an-email' });
expect(res.statusCode).toBe(400);
expect(res.body.errors).toBeDefined();
});npm testor
npx jest/tests
├── app.test.js
├── users.test.js
└── utils.test.jsRun with:
npx jest --coverageOutput:
File | % Stmts | % Branch | % Funcs | % Lines
utils/math.js| 100 | 100 | 100 | 100
-
Use
.envfiles for sensitive configs. -
Avoid hardcoding ports, DB URIs, secrets.
-
Enable production middleware:
helmet,compression,cors,rate-limit.
npm install -g pm2pm2 start app.js --name="express-app"pm2 list # Show running apps
pm2 stop express-app # Stop app
pm2 restart express-app # Restart app
pm2 logs express-app # View logs
pm2 delete express-app # Delete apppm2 startup
pm2 saveserver {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}- Install NGINX:
sudo apt install nginx-
Add config to
/etc/nginx/sites-available/yourdomain.com. -
Symlink:
sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/- Reload NGINX:
sudo nginx -t
sudo systemctl reload nginxsudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.comAuto-renew:
sudo certbot renew --dry-runFROM node:18-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["node", "app.js"]docker build -t express-app .
docker run -p 3000:3000 express-appversion: '3'
services:
app:
build: .
ports:
- "3000:3000"
env_file:
- .envRun:
docker-compose up --buildPORT=3000
DB_URI=mongodb://...
JWT_SECRET=...
NODE_ENV=productionLoad with:
require('dotenv').config();name: CI/CD Pipeline
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
cd /var/www/express-app
git pull
npm install --production
pm2 restart express-app-
✅ Environment variables configured.
-
✅ Enable rate limiting, helmet, compression.
-
✅ Use NGINX for reverse proxy + HTTPS.
-
✅ Use PM2 or Docker for process management.
-
✅ Configure CI/CD for auto-deployment.
-
For real-time bidirectional communication.
-
Use cases:
-
Chat apps
-
Notifications
-
Live updates
-
IoT streams
-
Multiplayer games
-
npm install socket.ioconst express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: '*', // Change this to your frontend origin in production
methods: ['GET', 'POST']
}
});
io.on('connection', (socket) => {
console.log('New client connected:', socket.id);
socket.on('message', (data) => {
console.log('Message from client:', data);
io.emit('message', data); // Broadcast to all clients
});
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
});
});
app.get('/', (req, res) => {
res.send('WebSocket server running');
});
server.listen(3000, () => {
console.log('Server listening on http://localhost:3000');
});<!DOCTYPE html>
<html>
<head>
<title>Socket.IO Client</title>
</head>
<body>
<h1>Socket.IO Test</h1>
<input id="msgInput" placeholder="Type a message..." />
<button onclick="sendMessage()">Send</button>
<ul id="messages"></ul>
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<script>
const socket = io('http://localhost:3000');
socket.on('connect', () => {
console.log('Connected as', socket.id);
});
socket.on('message', (msg) => {
const li = document.createElement('li');
li.innerText = msg;
document.getElementById('messages').appendChild(li);
});
function sendMessage() {
const input = document.getElementById('msgInput');
const msg = input.value;
socket.emit('message', msg);
input.value = '';
}
</script>
</body>
</html>const chatNamespace = io.of('/chat');
chatNamespace.on('connection', (socket) => {
console.log('Chat client connected:', socket.id);
});Client connects via:
const socket = io('http://localhost:3000/chat');io.on('connection', (socket) => {
socket.on('joinRoom', (room) => {
socket.join(room);
});
socket.on('message', ({ room, message }) => {
io.to(room).emit('message', message);
});
});- User joins room
room123:
socket.emit('joinRoom', 'room123');- Send message to that room:
socket.emit('message', { room: 'room123', message: 'Hello group' });- All users in
room123receive:
socket.on('message', (msg) => {
console.log('Room message:', msg);
});- Use Redis adapter for scaling sockets across multiple servers.
npm install socket.io-redis-
Secure WebSocket with HTTPS.
-
Handle reconnections and errors gracefully.
-
Build scalable, maintainable, and production-ready Express apps.
-
Implement Clean Code and SOLID principles.
-
Decouple business logic from infrastructure (DB, framework).
/src
├── config/ # Configurations (DB, CORS, etc.)
├── controllers/ # Request handlers
├── models/ # DB models (Mongoose, Sequelize, etc.)
├── routes/ # Route definitions
├── services/ # Business logic layer
├── middlewares/ # Auth, error handling, validators
├── utils/ # Helpers, utilities, constants
├── jobs/ # Cron jobs, background tasks
├── app.js # App setup
└── server.js # Server start file| Folder/File | Responsibility |
| | |
| /config/ | DB, environment, CORS, 3rd party configs |
| /controllers/ | HTTP request handling |
| /models/ | Schema definitions (MongoDB, SQL) |
| /routes/ | API endpoints |
| /services/ | Business logic (decoupled from controllers) |
| /middlewares/ | Error handling, auth, validation |
| /utils/ | Common functions, constants |
| /jobs/ | Scheduled tasks (e.g., node-cron) |
const userService = require('../services/userService');
exports.getUsers = async (req, res, next) => {
try {
const users = await userService.getAllUsers();
res.json(users);
} catch (err) {
next(err);
}
};const User = require('../models/User');
exports.getAllUsers = async () => {
return await User.find();
};middlewares/errorHandler.js
module.exports = (err, req, res, next) => {
console.error(err);
const status = err.statusCode || 500;
res.status(status).json({
error: {
message: err.message || 'Internal Server Error'
}
});
};In app.js:
const errorHandler = require('./middlewares/errorHandler');
app.use(errorHandler);const asyncHandler = fn => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);routes/userRoutes.js
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
const asyncHandler = require('../middlewares/asyncHandler');
router.get('/', asyncHandler(userController.getUsers));
module.exports = router;In app.js:
const userRoutes = require('./routes/userRoutes');
app.use('/api/v1/users', userRoutes);config/db.js
const mongoose = require('mongoose');
const connectDB = async () => {
await mongoose.connect(process.env.MONGO_URI);
console.log('MongoDB connected');
};
module.exports = connectDB;In server.js:
require('dotenv').config();
const connectDB = require('./src/config/db');
connectDB();- Keep
controllers,services,routesisolated.
- Use
dotenvwith/configmanagement.
- Centralized middleware-based error management.
- No
.then().catch()in modern code.
- Use
express-validatorin middleware.
- Apply helmet, CORS, rate limiting, sanitize inputs.
- Use
winstonorpinofor logs instead ofconsole.log.
- Enforce code style with ESLint + Prettier.
- Separate unit and integration tests with Jest + Supertest.
For very large apps:
-
Apply Clean Architecture or Hexagonal Architecture.
-
Example Layers:
-
Entity Layer: Pure business models (no DB/framework).
-
Use Cases / Service Layer: Business rules.
-
Interface Layer: HTTP, CLI, WebSocket handlers.
-
Infrastructure Layer: DB, external APIs, file systems.
-
-
✅ Secure with CORS, Helmet, Rate-Limit.
-
✅ Error handling and logging in place.
-
✅ Env vars via
.env. -
✅ Health-check endpoint (
/api/health). -
✅ Proper CI/CD pipeline.
-
✅ PM2 or Docker for process management.
I’m happy to welcome contributions. Whether it's a bug fix, a new feature, or even improving the documentation—your input is appreciated!
Feel free to fork the repo, create a pull request, or open an issue if you have suggestions or questions.