src/
β
βββ config/ # Configuration files (DB, Cloudinary, etc.)
βββ constants.js # App constants
βββ controllers/ # Route controllers
βββ middleware/ # Custom middlewares
βββ models/ # Mongoose models
βββ routes/ # Express routes
βββ utils/ # Utility functions
βββ database/ # DB connection logic
βββ constants.js # App constants
βββ app.js # Express app
βββ index.js # Entry point
Note: Empty folders are tracked using
.gitkeepfiles. These ensure structure is preserved in Git.
git clone https://github.com/your-username/backend_Professional-Level-Project.git
cd backend_Professional-Level-Project
npm install
cp .env.example .env"scripts": {
Β "dev": "nodemon -r dotenv/config --experimental-json-modules src/index.js"
}Use the development script to run the server with environment variables auto-loaded.
Environment-specific secrets and configurations are placed inside a .env file.
MONGODB_URI=your_mongodb_uri
PORT=8000
CORS_ORIGIN=http://localhost:3000
ACCESS_TOKEN_SECRET=your_access_token
REFRESH_TOKEN_SECRET=your_refresh_token
ACCESS_TOKEN_EXPIRATION=15m
REFRESH_TOKEN_EXPIRATION=7d
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secretYou can generate strong JWT secrets using:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"In Express.js, asynchronous route handlers that throw errors (e.g., failed DB operations) donβt automatically trigger the error-handling middleware. Thatβs where asyncHandler comes in.
It is a higher-order function (a function that returns another function) that helps:
- Automatically catch errors from async functions.
- Forward them to the default Express error handler via
next(error). - Avoid repetitive
try...catchblocks in each route or controller.
const asyncHandler = (fn) => async (req, res, next) => {
try {
await fn(req, res, next);
} catch (error) {
res.status(error.code || 500).json({
success: false,
message: error.message || "Internal Server Error",
error: error,
});
next(error);
}
};
β οΈ Fix: Changeerr.codetoerror.code.
/**
* Wraps an async route/controller function and forwards any errors to Express error handler
* Avoids boilerplate try-catch blocks across the app.
*/
const asyncHandler = (fn) => {
return async (req, res, next) => {
try {
await fn(req, res, next);
} catch (error) {
console.error("Error caught in asyncHandler:", error);
res.status(error.code || 500).json({
success: false,
message: error.message || "Something went wrong",
error: process.env.NODE_ENV === "development" ? error : {},
});
next(error);
}
};
};
export { asyncHandler };// utils/ErrorHandler.js
class ErrorHandler extends Error {
constructor(message, code) {
super(message);
this.code = code; // Maintains proper stack trace
Error.captureStackTrace(this, this.constructor);
}
}
export default ErrorHandler;Use in routes like:
throw new ErrorHandler("User not found", 404);JWT (JSON Web Token) is a stateless authentication mechanism consisting of:
- Header (Algorithm & Type)
- Payload (user data)
- Signature (secured with secret)
π Secret Key Generator:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Set it in .env:
ACCESS_TOKEN_SECRET=your_generated_secret
REFRESH_TOKEN_SECRET=your_refresh_token_secretπ Tokens are Bearer tokens β the one who bears it has access to resources.
- Short-lived (e.g., 15 min)
- Used in API headers to authenticate users
- Long-lived (e.g., 7 days)
- Used to issue new access tokens
- Stored securely (HTTP-only cookies recommended)
ACCESS_TOKEN_EXPIRATION=15m
REFRESH_TOKEN_EXPIRATION=7dUse cloud storage services like:
- AWS S3
- Cloudinary
- Google Cloud Storage
- Azure Blob Storage
multerβ Used to parsemultipart/form-datafor file uploadscloudinaryβ Used for image/file hosting and transformation
Example Cloudinary Config:
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
});A pagination plugin for Mongoose aggregation pipelines.
import mongoose from "mongoose";
import aggregatePaginate from "mongoose-aggregate-paginate-v2";
const MySchema = new mongoose.Schema({
name: String,
age: Number,
});
MySchema.plugin(aggregatePaginate);
export default mongoose.model("User", MySchema);const options = { page: 1, limit: 10 };
const aggregate = User.aggregate();
User.aggregatePaginate(aggregate, options)
.then((results) => console.log(results))
.catch((err) => console.error(err));User.aggregatePaginate(aggregate, options, (err, results) => {
if (err) return console.error(err);
console.log(results);
});Install:
npm i -D prettierCreate .prettierrc:
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5"
}"scripts": {
Β "dev": "nodemon -r dotenv/config --experimental-json-modules src/index.js"
}This section explains how to handle file uploads in a production-grade Node.js application using Multer for local storage and Cloudinary for cloud storage. The system first stores the uploaded file temporarily on the server and then uploads it to Cloudinary. This approach offers better fault tolerance, allowing for retries in case of upload errors.
- User uploads a file via a client (form or API call).
- Multer stores the file temporarily in a specified local folder.
- The server uploads the file from local storage to Cloudinary.
- On success, the Cloudinary URL is saved in the database (optional).
- The local file can be optionally deleted after successful upload.
const multer = require("multer");
const path = require("path");
// Configure local storage
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "uploads/"); // Make sure this folder exists
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
const ext = path.extname(file.originalname); // Preserve original extension
cb(null, file.fieldname + "-" + uniqueSuffix + ext);
},
});
const upload = multer({ storage: storage });β Note: Multer does not create directories when using a custom
destinationfunction. Ensure theuploads/folder exists beforehand.
const cloudinary = require("cloudinary").v2;
const fs = require("fs");
// Configure cloudinary
cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.CLOUD_API_KEY,
api_secret: process.env.CLOUD_API_SECRET,
});
// Upload from local storage to cloudinary
const uploadToCloudinary = async (localFilePath) => {
try {
const result = await cloudinary.uploader.upload(localFilePath, {
folder: "your-folder-name",
});
// Optional: delete local file after successful upload
fs.unlinkSync(localFilePath);
return result;
} catch (error) {
throw new Error("Cloudinary upload failed");
}
};const express = require("express");
const router = express.Router();
router.post("/upload", upload.single("file"), async (req, res) => {
try {
const localPath = req.file.path;
const cloudinaryResponse = await uploadToCloudinary(localPath);
res.status(200).json({
message: "File uploaded successfully",
cloudinaryUrl: cloudinaryResponse.secure_url,
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});- Redundancy: Local backup enables retry on failed cloud uploads.
- Traceability: Files are stored with unique names and extensions.
- Security: Files are not stored permanently on the server.
- Scalability: Upload logic is modular and reusable.
-
β Organized codebase inside
srcfolder. -
β MongoDB connected via Mongoose with proper async-await & try-catch for DB security.
-
β Uses dotenv for environment configuration.
-
β Integrates JWT for secure auth (access & refresh tokens).
-
β Image/file handling using Multer and Cloudinary.
-
β CORS enabled for secure cross-origin access:
app.use(cors({ origin: process.env.CORS_ORIGIN }));
-
β Limits large incoming JSON:
app.use(express.json({ limit: "16kb" }));
-
β Cookie support via:
import cookieParser from "cookie-parser"; app.use(cookieParser());
-
β Uses Prettier for consistent formatting:
-
Install:
npm i -D prettier -
Create
.prettierrc:{ "semi": true, "singleQuote": true, "tabWidth": 2, "trailingComma": "es5" }
-
-
β Follows production security: DB access IP-limited in MongoDB Atlas.
-
- β
Keep
.envsecure and gitignored
- β
Keep
-
β Use
.gitkeepto track empty folders -
β Modularize code into
srcwith subfolders -
β Use
corsfor restricted API access -
β Apply size limits to incoming requests:
app.use(express.json({ limit: "16kb" }));
-
β Store access & refresh token expiry values in
.env -
β Restrict MongoDB Atlas access to IP ranges (never
0.0.0.0/0in production) -
β Always catch async DB errors in routes or use
asyncHandler
This section provides a comprehensive overview of web standards around HTTP, including the purpose of headers, common types, security practices, CORS configuration, HTTP methods with examples, and standard response codes.
| Term | Full Form | Description | Example |
|---|---|---|---|
| URL | Uniform Resource Locator | Specifies the location of a resource | https://example.com/index.html |
| URI | Uniform Resource Identifier | Identifies a resource either by name or location (or both) | https://example.com, urn:isbn:0451450523 |
| URN | Uniform Resource Name | Names the resource without indicating its location | urn:isbn:0451450523 |
Headers are key-value pairs sent with HTTP requests/responses. They provide metadata to help the client or server understand how to process the data.
They are used for:
- π Authentication
- βοΈ Managing state
- π§ Caching
- π§© Content Negotiation
Before 2012, custom headers often started with X- (e.g. X-Custom-Token). Since then, the standard has deprecated the use of X- prefixes in favor of meaningful names.
Example:
X-Powered-By: Express // β Deprecated pattern
Server: Express // β
Modern pattern- Request Headers: Sent by the client (browser or Postman).
- Response Headers: Sent by the server (Express, Django, etc.).
- Representation Headers: Describe the encoding or compression.
- Payload Headers: Describe the body data being transferred.
| Header | Description |
|---|---|
Accept |
Tells the server what content types the client can process. Example: application/json |
User-Agent |
Identifies the application making the request (e.g., Chrome, Postman) |
Authorization |
Carries credentials for authentication (e.g., Bearer Token) |
Content-Type |
Describes the format of the request/response body |
Cookie |
Contains stored session or preference information |
Cache-Control |
Controls how caching is handled by the browser or proxy |
Cross-Origin Resource Sharing allows restricted resources on a web page to be requested from another domain.
| Header | Purpose |
|---|---|
Access-Control-Allow-Origin |
Specifies the origin(s) allowed |
Access-Control-Allow-Credentials |
Allows sending of cookies and auth headers |
Access-Control-Allow-Methods |
Lists allowed HTTP methods (e.g., GET, POST) |
| Header | Purpose |
|---|---|
Cross-Origin-Embedder-Policy |
Prevents unauthorized resources from being embedded |
Cross-Origin-Opener-Policy |
Isolates browsing context for improved security |
Content-Security-Policy |
Prevents XSS attacks by restricting sources |
X-XSS-Protection |
Activates browserβs XSS protection mechanisms |
| Method | Description | Example |
|---|---|---|
GET |
Retrieve a resource | GET /users β Fetch all users |
HEAD |
Fetch only headers | HEAD /users β Get metadata only |
OPTIONS |
Discover supported methods | OPTIONS /users β Returns: GET, POST |
TRACE |
Debug, echoes back request | Rarely used in modern APIs |
DELETE |
Remove a resource | DELETE /users/123 β Delete user with ID 123 |
PUT |
Replace a resource | PUT /users/123 with full user object |
POST |
Create a new resource | POST /users with user data |
PATCH |
Modify part of a resource | PATCH /users/123 with name update |
Example (Express.js POST endpoint):
app.post("/users", (req, res) => {
const { name, email } = req.body;
// Save to DB
res.status(201).json({ message: "User created" });
});Status codes indicate the result of an HTTP request. They are grouped into five categories:
| Class | Description |
|---|---|
1xx |
Informational (e.g., 100 Continue) |
2xx |
Success (e.g., 200 OK, 201 Created) |
3xx |
Redirection (e.g., 307 Temporary Redirect) |
4xx |
Client Errors (e.g., 404 Not Found) (Client Did Not Send Correct Information) |
5xx |
Server Errors (e.g., 500 Internal Server Error) |
| Code | Meaning |
|---|---|
100 |
Continue |
102 |
Processing |
200 |
OK |
201 |
Created |
202 |
Accepted |
307 |
Temporary Redirect |
308 |
Permanent Redirect |
400 |
Bad Request |
401 |
Unauthorized |
402 |
Payment Required |
404 |
Not Found |
500 |
Internal Server Error |
504 |
Gateway Timeout |
- β Never push
.envor secrets β use.gitignoreto exclude them. - β In MongoDB Atlas, restrict access to your server IP only.
- β
Place all DB connections in
try-catchblocks to avoid crashes. - β
Serve secure headers with packages like
helmet.
To take your backend development to the next level:
- π System Design
- ποΈ Database Design
- π Code Optimization
- π§© Debugging & Profiling
This project is open source and available under the MIT License.