Imagine one morning the AQAD platform is running smoothly.
Vendors are uploading products.
Retailers are placing orders.
Payments are being processed.
Delivery partners are receiving delivery requests.
Everything looks perfect.
Then suddenly a retailer clicks the "Place Order" button and sees:
"Something went wrong."
No explanation. No guidance.
No error details.The order is not placed.
The retailer becomes frustrated. The support team receives complaints.
Developers start checking logs.
Nobody knows what actually happened.
This is exactly why error handling exists.
In real-world software development, errors are not optional.
Errors are guaranteed. Servers fail. Databases disconnect.
Users enter invalid data. Networks become unstable.
Third-party services stop responding.
The difference between a professional backend and a beginner backend is not whether errors occur.
The difference is how those errors are handled.
we will learn how professional Node.js applications manage errors gracefully using Express.
What Is Error Handling?
Error handling is the process of detecting, managing, and responding to unexpected problems inside an application.
Instead of allowing the application to crash, we catch errors and respond appropriately.
Think about a hospital emergency department.
Patients arrive with different problems.
Some have minor injuries.
Some have serious conditions.
Some need immediate attention.
The hospital cannot panic every time a patient arrives.
It follows a structured process.
Receive the patient. Identify the issue.
Provide treatment. Record what happened.
Error handling works the same way.
An application receives a problem.
Identifies the issue . Responds appropriately.
Logs the problem. Continues serving users.
Why Error Handling Matters
Without proper error handling:
Applications crash.
Users become confused.
Security risks increase.
Debugging becomes difficult.
Customer trust decreases.
Imagine AQAD receives 10,000 orders daily.
If a single invalid request crashes the entire backend, thousands of users are affected.
Professional systems never allow this.
They isolate errors and recover gracefully.
Types of Errors in Node.js
Most backend applications encounter several categories of errors.
Let's understand them one by one.
1. Syntax Errors
Syntax errors occur when JavaScript code violates language rules.
Example:
const user = {
name: "Ahmed"
Missing closing bracket.
Node.js cannot understand the code.
Application will fail before execution starts.
These errors are usually detected during development.
2. Runtime Errors
Runtime errors happen while the application is running.
Example:
const user = null;
console.log(user.name);
Output:
TypeError: Cannot read properties of null
The code starts successfully.
But fails during execution.
These are very common in backend systems.
3. Validation Errors
Validation errors occur when users send invalid data.
Example:
A retailer tries creating an account.
Request:
{
"email": "abc"
}
Valid email expected.
Invalid email received.
Instead of crashing, backend should respond:
{
"success": false,
"message": "Invalid email address"
}
This is a validation error.
4. Database Errors
Database errors occur when communication with the database fails.
Examples:
Database server unavailable.
Invalid query.
Connection timeout.
Missing records.
AQAD Example:
A retailer requests product information.
Backend asks database for product details.
Database connection is lost.
Application should return:
{
"success": false,
"message": "Database temporarily unavailable"
}
instead of crashing.
5. Authentication Errors
Authentication errors occur when identity verification fails.
Example:
{
"message": "Invalid email or password"
}
User credentials are incorrect.
System should reject access.
6. Authorization Errors
Authentication and authorization are different.
Authentication:
"Who are you?"
Authorization:
"What are you allowed to do?"
AQAD Example:
Retailer tries deleting another vendor's product.
Backend response:
{
"message": "Access denied"
}
This is an authorization error.
Understanding try-catch
Node.js provides a mechanism called try-catch.
It allows us to capture errors without crashing the application.
Example:
try {
const result = user.name;
}
catch(error) {
console.log(error.message);
}
How it works:
Step 1 : Code inside try executes.
Step 2 : If an error occurs, execution jumps to catch.
Step 3 : Error information becomes available.
Step 4 : Application continues running.
Real AQAD Example
Suppose a product lookup is performed.
try {
const product = await Product.findById(id);
res.json(product);
}
catch(error){
res.status(500).json({
message: "Failed to fetch product"
});
}
Instead of crashing, the server sends a proper response.
Understanding Error Objects
Whenever JavaScript encounters an error, it creates an Error object.
Example:
try {
throw new Error("Inventory not found");
}
catch(error){
console.log(error.message);
}
Output:
Inventory not found
Error objects contain useful information.
error.message
error.name
error.stack
Example:
console.log(error.name);
console.log(error.message);
console.log(error.stack);
These details help developers debug problems quickly.
Creating Custom Errors
Professional applications often create custom error messages.
Example:
throw new Error("Vendor account suspended");
AQAD Example:
if(!vendor.isActive){
throw new Error("Vendor account inactive");
}
This makes debugging easier.
Express Default Error Handling
Express already contains built-in error handling.
Example:
app.get("/", () => {
throw new Error("Server Error");
});
Express catches the error.
However, the default response is not suitable for production systems.
Professional APIs require custom error responses.
Custom Error Handling Middleware
One of Express's most powerful features is centralized error handling.
Instead of handling errors everywhere, we create a dedicated middleware.
Example:
app.use((err, req, res, next) => {
res.status(500).json({
success: false,
message: err.message
});
});
Notice four parameters:
err
req
res
next
This identifies it as an error middleware.
How Error Middleware Works
Request arrives.
↓
Controller executes.
↓
Error occurs.
↓
Express forwards error.
↓
Error middleware runs.
↓
Response returned.
Think of it as AQAD's problem resolution desk.
Every complaint reaches one department.
That department decides the final response.
Using next(error)
Express provides the next() function.
When an error occurs:
next(error);
Example:
app.get("/products", async(req,res,next)=>{
try{
const products = await Product.find();
res.json(products);
}catch(error){
next(error);
}
});
The error moves directly to error middleware.
This keeps controllers clean and professional.
Centralized Error Handling
Beginners often write:
try-catch
try-catch
try-catch
try-catch
inside every route.
Large applications become difficult to maintain.
Professional systems centralize error handling.
Benefits:
Cleaner code
Consistent responses
Easier maintenance
Better debugging
Scalability
Standard API Error Responses
Good APIs provide consistent formats.
Bad Example:
{
"error": "Oops"
}
Good Example:
{
"success": false,
"message": "Product not found"
}
Even better:
{
"success": false,
"message": "Product not found",
"errorCode": "PRODUCT_NOT_FOUND"
}
This helps frontend developers handle responses correctly.
Handling 404 Errors
Users often request routes that don't exist.
Example:
/api/products/99999
Product may not exist.
Or route itself may not exist.
Create a 404 handler.
app.use((req,res)=>{
res.status(404).json({
success:false,
message:"Route not found"
});
});
AQAD Example:
A retailer requests:
/api/orders/unknown
Response:
{
"message": "Order not found"
}
Operational Errors vs Programming Errors
Professional backend developers distinguish between two categories.
Operational Errors
Expected problems.
Examples: Invalid password
Missing product
Network timeout
Invalid request
Programming Errors
Developer mistakes.
Examples:
Undefined variable
Wrong function call
Incorrect logic
Operational errors should be handled gracefully.
Programming errors should be fixed immediately.
Async Error Handling
Modern Node.js applications heavily use async-await.
Example:
app.get("/vendors", async(req,res)=>{
const vendors = await Vendor.find();
res.json(vendors);
});
What if database fails?
Unhandled rejection occurs.
We need:
try {
}
catch(error) {
}
or centralized wrappers.
Async Wrapper Function
Professional applications often create helper functions.
Example:
const asyncHandler = (fn) =>
(req,res,next) =>
Promise.resolve(fn(req,res,next))
.catch(next);
Usage:
app.get(
"/products",
asyncHandler(async(req,res)=>{
const products = await Product.find();
res.json(products);
})
);
Benefits:
Less repetitive code.
Cleaner controllers.
Centralized error handling.
Logging Errors
Never hide errors.
Always log them.
Example:
console.error(error);
Production systems typically use logging tools such as:
Morgan
Winston
Pino
CloudWatch
Elastic Stack
We will explore logging deeply in the next chapter.
AQAD Error Handling Architecture
Let's imagine the AQAD backend flow.
Retailer submits order.
↓
Validation Middleware
↓
Authentication Middleware
↓
Authorization Middleware
↓
Controller
↓
Database
↓
Response
At every stage an error can occur.
Invalid data
↓
Validation Error
Invalid token
↓
Authentication Error
No permission
↓
Authorization Error
Database failure
↓
Database Error
Unknown issue
↓
Internal Server Error
All errors eventually reach one centralized error middleware.
This is exactly how enterprise applications operate.
Common Error Status Codes
Client sends invalid data.
{
"message": "Email is required"
}
Authentication failed.
{
"message": "Invalid token"
}
Authenticated but not allowed.
{
"message": "Permission denied"
}
Requested resource missing.
{
"message": "Product not found"
}
Unexpected server issue.
{
"message": "Something went wrong"
}
Always use centralized error middleware.
Never expose sensitive server information.
Provide meaningful messages.
Log all unexpected errors.
Use proper HTTP status codes.
Validate user input.
Handle database failures.
Create reusable error utilities.
Keep responses consistent.
Test failure scenarios.
Mistake 1
Returning generic responses everywhere.
Error
Not useful.
Mistake 2
Ignoring async errors.
Mistake 3
Exposing database details to users.
Bad:
{
"message": "SQL connection failed at port 3306"
}
Mistake 4
Using wrong status codes.
Everything should not be 500.
Mistake 5
No centralized error middleware.
Creates maintenance problems.
Mini Exercise
Imagine AQAD has an API:
POST /orders
What should happen when:
Product does not exist?
User token is invalid?
Quantity is negative?
Database is unavailable?
Think about:
Status Code
Error Message
Response Structure
This exercise helps you design production-grade APIs.
Logging in Node.js and Express
Imagine it is Monday morning.
The AQAD platform is processing thousands of activities.
Vendors are uploading products.
Retailers are placing orders.
Delivery partners are updating shipment statuses.
Payments are being processed.
Everything seems normal.
Then suddenly the support team receives a complaint:
"My order was placed yesterday, but today it has disappeared."
The support team checks the database.
The order exists.
The payment exists.
The retailer account exists.
Yet somehow the order status changed unexpectedly.
Now developers start investigating.
The first question they ask is:
"What happened?"
The second question is:
"When did it happen?"
The third question is:
"Who triggered it?"
Without logs, these questions become extremely difficult to answer.
With proper logging, the answers are often available within minutes.
This is why logging is one of the most important practices in backend development.
Many beginner developers think logging simply means writing:
console.log("Hello");
In reality, professional logging is much more powerful.
Large companies like Amazon, Netflix, Uber, and Google rely heavily on logs to monitor and troubleshoot their systems.
Let's understand how logging works in professional Node.js applications.
What Is Logging?
Logging is the process of recording important events that occur inside an application.
Think of logs as a diary for your backend.
Every important action gets recorded.
Examples:
User login
Product creation
Order placement
Payment success
Payment failure
Database errors
API requests
Server startup
Server shutdown
Logs help developers understand exactly what happened inside the system.
Real World Analogy: Security Camera System
Imagine a shopping mall.
The mall installs hundreds of security cameras.
Why?
Not because something bad happens every day.
But because when something does happen, management needs evidence.
Questions can be answered:
Who entered?
When did they enter?
Which floor did they visit?
What happened before the incident?
Logs serve the same purpose.
Your backend records events so developers can investigate issues later.
Without logs, debugging becomes guesswork.
Why Logging Is Important
Many backend issues occur unexpectedly.
A retailer reports:
"My order was charged twice."
A vendor says:
"My inventory count suddenly changed."
A delivery partner says:
"I never received that delivery request."
Developers need facts.
Not assumptions.
Logs provide those facts.
Benefits include:
Debugging issues
Monitoring application health
Tracking user activity
Identifying security threats
Finding performance bottlenecks
Auditing system changes
Troubleshooting production problems
What Should Be Logged?
A common beginner mistake is logging everything.
Another common mistake is logging nothing.
Professional systems log meaningful events.
Examples include:
Application startup
Server shutdown
User authentication
API requests
Database failures
Payment events
Authorization failures
File uploads
External API calls
System warnings
Unexpected errors
AQAD Example
Suppose a retailer places an order.
A log entry could look like:
2026-06-14 10:30:15
Retailer 123 created Order ORD-1001
When payment succeeds:
2026-06-14 10:31:08
Payment successful for Order ORD-1001
When delivery is assigned:
2026-06-14 10:45:02
Delivery Partner 55 assigned to Order ORD-1001
By reading logs, developers can reconstruct the entire journey of the order.
Understanding Log Levels
Not all logs are equally important.
Professional logging systems categorize logs into levels.
Info Logs
General system information.
Example:
Server started successfully
or
Retailer logged in successfully
These are normal operational events.
Warning Logs
Potential issues that are not critical.
Example:
Vendor uploaded product without image
The system still works.
But developers may want to investigate.
Error Logs
Something failed.
Example:
Database connection failed
or
Payment gateway timeout
Immediate attention may be required.
Debug Logs
Used during development.
Example:
Received request payload
Debug logs provide detailed information.
Usually disabled in production environments.
Using console.log()
Every developer starts with:
console.log("Server Started");
Example:
const express = require("express");
const app = express();
console.log("Application Starting...");
Output:
Application Starting...
This is useful during learning.
However, large applications need more advanced solutions.
Problems with console.log()
As applications grow:
Thousands of requests occur.
Multiple servers run simultaneously.
Different developers work together.
Finding useful information becomes difficult.
Example:
console.log("User Logged In");
console.log("Order Created");
console.log("Payment Success");
console.log("Database Error");
After millions of requests, logs become messy.
Professional applications need structured logging.
Request Logging
One of the most common logging requirements is request tracking.
Every incoming API request should be recorded.
Example:
GET /products
POST /orders
PUT /inventory
DELETE /users
This helps developers understand:
Who accessed the API
Which routes were used
Response times
Request frequency
System load
Introducing Morgan
Morgan is one of the most popular logging middleware packages for Express.
Its purpose is simple:
Log every incoming request.
Installation:
npm install morgan
Usage:
const morgan = require("morgan");
app.use(morgan("dev"));
Now every request is automatically logged.
Example output:
GET /products 200 15ms
POST /orders 201 25ms
No extra coding required.
How Morgan Helps
Suppose AQAD receives 50,000 API requests daily.
Morgan automatically records:
HTTP Method
Route
Status Code
Response Time
Request Information
Example:
POST /api/orders 201 42ms
Developers immediately know:
Order endpoint was called.
Response succeeded.
Request took 42 milliseconds.
Morgan Formats
Morgan provides multiple formats.
Tiny Format
app.use(morgan("tiny"));
Example:
GET /products 200
Combined Format
app.use(morgan("combined"));
Provides more detailed information.
Useful in production systems.
Dev Format
app.use(morgan("dev"));
Most commonly used during development.
Easy to read.
Color-coded output.
What Morgan Cannot Do
Morgan is excellent for request logging.
But it is not a complete logging solution.
It cannot efficiently manage:
Business events
Payment logs
Authentication logs
Custom application logs
Error logs
For that, we need something more powerful.
Introducing Winston
Winston is one of the most popular logging libraries in Node.js.
Installation:
npm install winston
Think of Morgan as a receptionist.
Think of Winston as an entire record management department.
Morgan records visitors.
Winston records everything.
Creating a Winston Logger
Example:
const winston = require("winston");
const logger = winston.createLogger({
level: "info",
transports: [
new winston.transports.Console()
]
});
Now:
logger.info("Server Started");
Output:
info: Server Started
Logging Different Levels
Example:
logger.info("Order Created");
logger.warn("Inventory Low");
logger.error("Database Failed");
Output:
info: Order Created
warn: Inventory Low
error: Database Failed
This organization makes troubleshooting easier.
Writing Logs to Files
Professional systems store logs permanently.
Example:
new winston.transports.File({
filename: "app.log"
})
Now logs are saved even after server restarts.
This is critical in production environments.
AQAD Logging Example
Suppose a retailer creates an order.
Controller:
logger.info(
"Retailer 123 created Order ORD-1001"
);
Payment Success:
logger.info(
"Payment completed for ORD-1001"
);
Failed Payment:
logger.error(
"Payment gateway timeout for ORD-1001"
);
All events become searchable.
Logging Errors
Every unexpected error should be logged.
Example:
try {
await createOrder();
}
catch(error){
logger.error(error.message);
}
Without logs:
Problem disappears.
With logs:
Problem becomes traceable.
Structured Logging
Professional applications avoid vague messages.
Bad:
logger.error("Error");
Good:
logger.error(
"Database connection failed while creating order"
);
Even better:
logger.error({
module: "Order Service",
orderId: "ORD-1001",
error: error.message
});
Structured logs are easier to search and analyze.
Monitoring Production Systems
Large systems generate millions of logs.
Developers use specialized tools.
Examples:
CloudWatch
Elastic Stack
Grafana
Datadog
Splunk
These platforms help visualize logs and system health.
AWS CloudWatch
If AQAD is hosted on AWS, logs can be sent to AWS CloudWatch.
Benefits:
Centralized monitoring
Searchable logs
Alerts
Performance tracking
Historical analysis
Developers can detect issues before customers notice them.
Logging Authentication Events
Authentication events are extremely important.
Example:
User Login Success
User Login Failed
Password Reset Requested
MFA Verification Failed
These logs help identify suspicious activity.
Logging Authorization Failures
Example:
Retailer attempted to access Vendor Dashboard
This may indicate:
Misconfiguration
Bug
Potential security threat
Authorization logs are valuable during security audits.
Logging API Performance
Suppose an API normally takes:
50ms
Suddenly it starts taking:
3000ms
Logs help detect this change.
Performance issues become visible before users complain.
Let's design a simple logging flow.
Retailer sends request
↓
Morgan logs request
↓
Authentication middleware logs login details
↓
Controller logs business events
↓
Database layer logs failures
↓
Error middleware logs exceptions
↓
Winston stores everything
↓
CloudWatch stores centralized records
This architecture resembles what many real-world systems use.
Common Logging Mistakes
Never log:
Passwords
Credit card information
OTP codes
JWT secrets
Private keys
Bad Example:
logger.info(password);
Never do this.
Too many logs create noise.
Log important information only.
Many beginners handle errors but never record them.
Always log critical failures.
Bad:
logger.error("Issue");
Good:
logger.error(
"Database timeout while fetching products"
);
Using only:
console.log()
for everything makes analysis difficult.
Use proper log levels.
Imagine AQAD has these events:
Vendor Login
Product Created
Order Created
Payment Failed
Inventory Running Low
For each event:
Determine whether it should be:
Info
Warning
Error
Think about why each category is appropriate.
This exercise teaches you how professional teams classify system events.
Validation with Joi and Express Validator
The Warehouse Receiving Department Analogy
Imagine AQAD operates a massive warehouse.
Every day vendors send products to the warehouse.
Packages arrive continuously.
Examples:
Pepsi
Rice Bags
Cooking Oil
Frozen Food
Baby Products
Now imagine the warehouse accepts everything without checking.
Soon problems appear.
Someone sends:
Quantity: -50
Another sends:
Price: -100
Someone forgets the product name.
Another vendor submits:
Email: abc
The warehouse becomes a mess.
Professional warehouses inspect incoming goods before accepting them.
Backend applications must do the same.
Before storing data:
We verify it.
This process is called:
Validation
What Is Validation?
Validation is the process of checking whether incoming data follows the expected rules.
In simple words:
Validation ensures that data is correct before the application processes it.
Why Validation Is Important
Without validation:
Bad data enters the system.
Bad data causes:
- Application errors
- Security vulnerabilities
- Database corruption
- Incorrect reports
- Unexpected crashes
Validation acts as the first line of defense.
Real AQAD Example
Vendor creates a product.
Request:
{
"name":"Pepsi",
"price":50,
"quantity":100
}
Looks valid.
Now imagine:
{
"name":"",
"price":-500,
"quantity":"hello"
}
Clearly invalid.
Validation catches these problems immediately.
Never Trust User Input
One of the most important backend rules:
Never trust user input.
Even if your frontend validates data,
you must validate again on the backend.
Why?
Because attackers can bypass frontend validation.
Frontend Validation vs Backend Validation
Many beginners think:
Frontend Validation Is Enough
This is incorrect.
Purpose:
Improve User Experience
Example:
Please enter email
shown instantly.
Purpose:
Security
Data Integrity
Backend validation is mandatory.
Frontend may prevent:
Price = -100
But an attacker can directly call:
POST /products
and send:
{
"price": -100
}
Only backend validation can stop this.
Most applications validate:
Name Required
user@gmail.com
Valid.
Password:
8 Characters Minimum
Product title:
100 Characters Maximum
Price must be numeric.
Quantity cannot be negative.
Product Name:
Required
Price:
Required
Positive Number
Quantity:
Required
Minimum 0
Category:
Required
These rules protect product quality.
Remember middleware?
Validation fits perfectly.
Flow:
Request
↓
Validation Middleware
↓
Controller
↓
Database
Bad data gets blocked early.
const validateProduct =
(req, res, next) => {
const {
name,
price
} = req.body;
if (!name) {
return res
.status(400)
.json({
message:
"Name Required"
});
}
if (price <= 0) {
return res
.status(400)
.json({
message:
"Invalid Price"
});
}
next();
};
Works.
But becomes difficult as projects grow.
This is why libraries exist.
Joi is one of the most popular validation libraries in Node.js.
Think of Joi as:
A professional rule-book for validating data.
Instead of writing long validation code manually,
we define rules.
Joi handles the rest.
npm install joi
Import:
const Joi = require("joi");
Ready to use.
A schema defines validation rules.
Example:
const schema = Joi.object({
name: Joi.string()
.required(),
price: Joi.number()
.positive()
.required()
});
This describes valid product data.
Name:
Joi.string()
.required()
Means:
Must Be String
Required
Price:
Joi.number()
.positive()
.required()
Means:
Must Be Number
Must Be Positive
Required
Request:
{
"name":"Pepsi",
"price":50
}
Validation:
const result =
schema.validate(req.body);
If valid:
result.error === undefined
Success.
Request:
{
"name":"",
"price":-50
}
Joi returns:
Validation Error
Request blocked.
Example:
const productSchema =
Joi.object({
title:
Joi.string()
.required(),
price:
Joi.number()
.positive()
.required(),
quantity:
Joi.number()
.min(0)
.required()
});
Professional.
Readable.
Reusable.
Example:
email:
Joi.string()
.email()
.required()
Valid:
vendor@aqad.com
Invalid:
vendor
Example:
password:
Joi.string()
.min(8)
.required()
Minimum eight characters required.
Another popular validation library.
Install:
npm install express-validator
It integrates directly with Express routes.
Example:
const {
body
} =
require(
"express-validator"
);
body("email")
.isEmail()
Meaning:
Email Must Be Valid
router.post(
"/register",
body("email")
.isEmail(),
registerUser
);
Simple.
Readable.
Popular.
Many beginners ask:
Which one should I use?
Both are excellent.
Best for:
Complex Validation
Reusable Schemas
Large Projects
Best for:
Route-Level Validation
Simple APIs
Quick Setup
Most large Node.js projects prefer Joi.
Sometimes built-in validation isn't enough.
Example:
AQAD Vendor Registration.
Business Rule:
Vendor Age >= 18
Custom validation can enforce this.
dob:
Joi.date()
Then:
Check Age >= 18
before approval.
Professional APIs return clear messages.
Bad:
{
"error":"Invalid"
}
Better:
{
"field":"email",
"message":"Valid Email Required"
}
Users immediately understand the issue.
Vendor submits:
{
"email":"vendor@aqad.com",
"password":"Vendor123",
"company":"ABC Trading"
}
↓
Validation Middleware
↓
Email Valid?
↓
Password Valid?
↓
Company Name Present?
↓
Pass?
↓
Controller Executes
↓
User Created
Request
↓
Validation Middleware
↓
Authentication Middleware
↓
Authorization Middleware
↓
Controller
↓
Database
Notice how validation happens very early.
Bad requests are stopped before reaching business logic.
Vendor sends:
{
"title":"Pepsi",
"price":15,
"quantity":100
}
↓
Validation Passes
↓
Database Insert
↓
Product Created
Now invalid request:
{
"title":"",
"price":-20,
"quantity":"abc"
}
↓
Validation Fails
↓
Response:
400 Bad Request
↓
Database Never Touched
Exactly what we want.
Relying only on frontend validation.
Not validating request parameters.
Example:
/products/abc
when ID should be numeric.
Returning vague error messages.
Skipping validation for internal APIs.
Writing validation inside controllers.
Keep validation separate.
Professional Validation Structure
project
│
├── validations
│ ├── productSchema.js
│ ├── userSchema.js
│ ├── orderSchema.js
│
├── middleware
│
├── routes
│
├── controllers
This structure scales beautifully.
Interview Questions
Validation ensures incoming data follows defined rules before processing.
Because frontend validation can be bypassed.
A Node.js validation library used to define and enforce data schemas.
An Express middleware library used for request validation.
File Uploads in Node.js and Express
Imagine a new vendor wants to join the AQAD platform.
During onboarding, the vendor must upload:
Trade License
VAT Certificate
Company Logo
Store Images
Bank Documents
Without these files, the vendor cannot be verified.
Now imagine another scenario.
A vendor wants to add a new product.
The product requires:
Product Images
Product Catalog PDF
Product Specifications
Marketing Materials
Again, files are required.
In modern applications, data is not limited to text.
Applications regularly handle:
Images
PDF documents
Videos
Audio files
Excel sheets
Invoices
Identity documents
Contracts
Reports
This is where file uploads become important.
A professional backend developer must know how to receive, validate, store, secure, and manage files properly.
we will learn how file uploads work in Node.js and Express using Multer, along with real-world AQAD examples.
Why File Uploads Matter
Think about how many applications you use daily.
Social media platforms require profile photos.
E-commerce platforms require product images.
Banking apps require identity documents.
Job portals require resumes.
Food delivery apps require restaurant menus.
Every modern application depends on file uploads.
Without file upload functionality, many business processes become impossible.
Real World Analogy: Warehouse Receiving Department
Imagine a large warehouse.
Every day hundreds of packages arrive.
The warehouse team does not blindly accept every package.
They perform several checks:
Who sent it?
What type of package is it?
Is the package damaged?
Is it allowed?
Where should it be stored?
File upload systems follow exactly the same process.
Before storing a file, the backend must verify:
File type
File size
File source
Storage destination
Security requirements
This prevents abuse and protects the application.
Let's understand the complete journey.
Vendor uploads a product image.
↓
Browser selects file.
↓
HTTP Request is created.
↓
File travels to server.
↓
Server validates file.
↓
File is stored.
↓
Database saves file information.
↓
Response is returned.
Although the process looks simple, many things happen behind the scenes.
Normally APIs send JSON.
Example:
{
"title": "iPhone 15",
"price": 1200
}
But JSON cannot efficiently carry large files.
When uploading files, browsers use:
multipart/form-data
This format allows:
Text fields
Images
PDFs
Videos
Documents
to travel together in a single request.
Example:
title = iPhone 15
price = 1200
image = iphone.jpg
Express handles JSON very well.
Example:
app.use(express.json());
But Express does not automatically process uploaded files.
For file uploads, we use a middleware called Multer.
Multer is a popular Node.js middleware used for handling multipart/form-data requests.
Think of Multer as the receiving department inside the warehouse.
Responsibilities:
Receive files
Validate files
Rename files
Store files
Pass information to the application
Without Multer, handling uploads becomes complicated.
Installation:
npm install multer
Import:
const multer = require("multer");
Now Express can process uploaded files.
Create storage:
const storage = multer.diskStorage({
destination: "./uploads",
filename: (req, file, cb) => {
cb(null, file.originalname);
}
});
Create uploader:
const upload = multer({
storage
});
Route:
app.post(
"/upload",
upload.single("image"),
(req, res) => {
res.json({
message: "File uploaded"
});
}
);
Simple and powerful.
Suppose AQAD allows vendors to upload a company logo.
Only one file is expected.
Example:
upload.single("logo")
This means:
Accept one file.
Field name must be:
logo
The uploaded file becomes available inside:
req.file
Example:
console.log(req.file);
Output:
{
originalname: "logo.png",
mimetype: "image/png",
size: 50000,
filename: "logo.png"
}
Useful information becomes available immediately.
Many business applications require multiple uploads.
AQAD Example:
Vendor uploads:
Front Product Image
Back Product Image
Side Product Image
Packaging Image
We can use:
upload.array("images", 5)
Meaning:
Accept up to 5 images.
Files become available through:
req.files
Suppose vendor onboarding requires:
Trade License
VAT Certificate
Company Logo
Each field accepts different files.
Example:
upload.fields([
{ name: "trade_license", maxCount: 1 },
{ name: "vat_certificate", maxCount: 1 },
{ name: "company_logo", maxCount: 1 }
])
This is extremely common in production systems.
Files must be stored somewhere.
Generally there are three approaches.
Files are stored directly on the server.
Example:
/uploads
Advantages:
Simple
Easy to learn
Fast setup
Disadvantages:
Difficult to scale
Server failure may lose files
Not ideal for large applications
Suitable for learning projects.
Files stored inside database.
Example:
MySQL BLOB
Advantages:
Centralized
Easy backups
Disadvantages:
Large databases
Reduced performance
Higher costs
Usually not recommended for large files.
Professional applications typically use:
Amazon S3
Google Cloud Storage
Azure Blob Storage
Advantages:
Highly scalable
Reliable
Cost effective
Fast delivery
This is the most common production approach.
In AQAD:
Vendor uploads product image.
↓
Backend receives image.
↓
Image stored in Amazon S3.
↓
Database stores image URL.
↓
Frontend loads image URL.
This is the architecture used by many modern applications.
A common beginner mistake:
logo.png
What happens when another vendor uploads:
logo.png
File collision occurs.
The previous file may be overwritten.
Professional systems generate unique names.
Example:
Date.now() + "-" + file.originalname
Result:
171800123-logo.png
Now every file is unique.
Never trust uploaded files blindly.
Suppose a vendor uploads:
virus.exe
while pretending it is an image.
This creates security risks.
Multer allows file type validation.
Example:
fileFilter: (req, file, cb) => {
if(
file.mimetype === "image/jpeg" ||
file.mimetype === "image/png"
){
cb(null, true);
}else{
cb(new Error("Invalid file type"));
}
}
Only approved files are accepted.
Suppose a user uploads:
5 GB video
Your server may become overwhelmed.
Always set limits.
Example:
limits: {
fileSize: 5 * 1024 * 1024
}
Meaning:
Maximum 5 MB.
If exceeded:
Upload rejected.
Product Images:
Maximum 5 MB
Allowed:
PNG
JPEG
WEBP
Rejected:
EXE
ZIP
BAT
This protects the platform.
Uploading a file is only half the process.
Usually we store metadata.
Example Product Table:
{
id: 101,
title: "Pepsi",
image_url: "/uploads/pepsi.png"
}
Database stores file location.
Actual image remains in storage.
This is more efficient.
Suppose files are stored locally.
Express can expose them.
Example:
app.use(
"/uploads",
express.static("uploads")
);
Now:
/uploads/logo.png
becomes publicly accessible.
Not every uploaded file should be public.
Public Examples:
Product Images
Company Logos
Marketing Banners
Private Examples:
Trade License
Bank Documents
Passport Copies
Emirates ID
Tax Certificates
Private files require authentication before access.
Many errors may occur.
Examples:
Invalid type
Large size
Corrupted file
Storage failure
Missing file
Always return meaningful responses.
Example:
{
"success": false,
"message": "Only PNG and JPEG files are allowed"
}
Avoid generic messages.
Uploading large images causes problems.
Large images mean:
More storage
Slower APIs
Longer loading times
Higher bandwidth costs
Professional systems optimize images.
Techniques include:
Compression
Resizing
Thumbnail generation
Format conversion
This improves user experience significantly.
Imagine a vendor uploads:
10 product images.
Backend process:
Receive images
↓
Validate types
↓
Validate size
↓
Generate unique names
↓
Upload to S3
↓
Save URLs in database
↓
Return success response
Every step is important.
Vendor onboarding requires:
Trade License
VAT Certificate
Bank Details
Emirates ID
Backend process:
Validate file
↓
Verify type
↓
Store securely
↓
Restrict public access
↓
Save metadata
↓
Associate with vendor account
These documents require stronger security controls.
File uploads are one of the most abused features in web applications.
Potential threats:
Malware uploads
Script injection
Huge file attacks
Unauthorized access
Fake file extensions
This is why validation is critical.
Always validate file type.
Always validate file size.
Generate unique filenames.
Use cloud storage for production.
Restrict access to sensitive files.
Store file metadata in database.
Scan suspicious files.
Never trust client-side validation alone.
Log upload activity.
Delete unused files.
Mistake 1
Accepting every file type.
Dangerous.
Mistake 2
No size limits.
Can crash server.
Mistake 3
Using original filenames.
Causes conflicts.
Mistake 4
Storing sensitive files publicly.
Major security issue.
Mistake 5
Not handling upload errors.
Creates poor user experience.
Imagine AQAD requires:
Product Images
Trade License
Company Logo
For each file determine:
Public or Private?
Maximum Size?
Allowed File Types?
Storage Location?
Think like a real backend architect.

0 Comments