Node.js Error Handling, Logging & File Upload Architecture



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?

Node.js Error Handling, Logging & File Upload Architecture


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

400 Bad Request

Client sends invalid data.

{
  "message": "Email is required"
}
401 Unauthorized

Authentication failed.

{
  "message": "Invalid token"
}
403 Forbidden

Authenticated but not allowed.

{
  "message": "Permission denied"
}
404 Not Found

Requested resource missing.

{
  "message": "Product not found"
}
500 Internal Server Error

Unexpected server issue.

{
  "message": "Something went wrong"
}

Best Practices for Error Handling

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.


Common Mistakes Beginners Make

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:

  1. Product does not exist?

  2. User token is invalid?

  3. Quantity is negative?

  4. 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.


AQAD Logging Architecture

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

Logging Sensitive Data

Never log:

Passwords

Credit card information

OTP codes

JWT secrets

Private keys

Bad Example:

logger.info(password);

Never do this.


Excessive Logging

Too many logs create noise.

Log important information only.


No Error Logging

Many beginners handle errors but never record them.

Always log critical failures.


Unclear Messages

Bad:

logger.error("Issue");

Good:

logger.error(
    "Database timeout while fetching products"
);

No Log Levels

Using only:

console.log()

for everything makes analysis difficult.

Use proper log levels.


Mini Exercise

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.


Frontend Validation

Purpose:

Improve User Experience

Example:

Please enter email

shown instantly.


Backend Validation

Purpose:

Security
Data Integrity

Backend validation is mandatory.


Example

Frontend may prevent:

Price = -100

But an attacker can directly call:

POST /products

and send:

{
"price": -100
}

Only backend validation can stop this.


Common Validation Rules

Most applications validate:

Required Fields
Name Required

Email Format
user@gmail.com

Valid.


Minimum Length

Password:

8 Characters Minimum

Maximum Length

Product title:

100 Characters Maximum

Numeric Validation

Price must be numeric.


Positive Numbers

Quantity cannot be negative.


AQAD Product Validation Rules

Product Name:

Required

Price:

Required
Positive Number

Quantity:

Required
Minimum 0

Category:

Required

These rules protect product quality.


Validation Middleware

Remember middleware?

Validation fits perfectly.

Flow:

Request

Validation Middleware

Controller

Database

Bad data gets blocked early.


Example Validation Middleware
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.


What Is Joi?

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.


Installing Joi
npm install joi

Import:

const Joi = require("joi");

Ready to use.


Creating Your First Joi Schema

A schema defines validation rules.

Example:

const schema = Joi.object({

name: Joi.string()
.required(),

price: Joi.number()
.positive()
.required()

});

This describes valid product data.


Understanding the Schema

Name:

Joi.string()
.required()

Means:

Must Be String
Required

Price:

Joi.number()
.positive()
.required()

Means:

Must Be Number
Must Be Positive
Required

Validating Data

Request:

{
"name":"Pepsi",
"price":50
}

Validation:

const result =
schema.validate(req.body);

If valid:

result.error === undefined

Success.


Invalid Example

Request:

{
"name":"",
"price":-50
}

Joi returns:

Validation Error

Request blocked.


AQAD Product Schema

Example:

const productSchema =
Joi.object({

title:
Joi.string()
.required(),

price:
Joi.number()
.positive()
.required(),

quantity:
Joi.number()
.min(0)
.required()

});

Professional.

Readable.

Reusable.


Email Validation

Example:

email:
Joi.string()
.email()
.required()

Valid:

vendor@aqad.com

Invalid:

vendor

Password Validation

Example:

password:
Joi.string()
.min(8)
.required()

Minimum eight characters required.


Express Validator

Another popular validation library.

Install:

npm install express-validator

Why Express Validator?

It integrates directly with Express routes.

Example:

const {
body
} =
require(
"express-validator"
);

Validation Example
body("email")
.isEmail()

Meaning:

Email Must Be Valid

Route Example
router.post(

"/register",

body("email")
.isEmail(),

registerUser

);

Simple.

Readable.

Popular.


Joi vs Express Validator

Many beginners ask:

Which one should I use?

Both are excellent.


Joi

Best for:

Complex Validation
Reusable Schemas
Large Projects

Express Validator

Best for:

Route-Level Validation
Simple APIs
Quick Setup

Most large Node.js projects prefer Joi.


Custom Validation Rules

Sometimes built-in validation isn't enough.

Example:

AQAD Vendor Registration.

Business Rule:

Vendor Age >= 18

Custom validation can enforce this.


Example
dob:
Joi.date()

Then:

Check Age >= 18

before approval.


Validation Error Responses

Professional APIs return clear messages.

Bad:

{
"error":"Invalid"
}

Better:

{
"field":"email",
"message":"Valid Email Required"
}

Users immediately understand the issue.


AQAD Registration Validation Flow

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 Lifecycle with Validation
Request

Validation Middleware

Authentication Middleware

Authorization Middleware

Controller

Database

Notice how validation happens very early.

Bad requests are stopped before reaching business logic.


Real AQAD Product Creation Example

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.


Common Validation Mistakes
Mistake 1

Relying only on frontend validation.


Mistake 2

Not validating request parameters.

Example:

/products/abc

when ID should be numeric.


Mistake 3

Returning vague error messages.


Mistake 4

Skipping validation for internal APIs.


Mistake 5

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

What is validation?

Validation ensures incoming data follows defined rules before processing.


Why is backend validation necessary?

Because frontend validation can be bypassed.


What is Joi?

A Node.js validation library used to define and enforce data schemas.


What is Express Validator?

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.


What Happens During a File Upload?

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.


Understanding Multipart Form Data

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

Why Express Cannot Handle Files Directly

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.


What Is 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.


Installing Multer

Installation:

npm install multer

Import:

const multer = require("multer");

Now Express can process uploaded files.


First File Upload Example

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.


Understanding upload.single()

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

Accessing Uploaded File Information

Example:

console.log(req.file);

Output:

{
  originalname: "logo.png",
  mimetype: "image/png",
  size: 50000,
  filename: "logo.png"
}

Useful information becomes available immediately.


Uploading Multiple Files

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

Uploading Different File Types

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.


Understanding Storage

Files must be stored somewhere.

Generally there are three approaches.


Option 1: Local Storage

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.


Option 2: Database Storage

Files stored inside database.

Example:

MySQL BLOB

Advantages:

Centralized

Easy backups

Disadvantages:

Large databases

Reduced performance

Higher costs

Usually not recommended for large files.


Option 3: Cloud Storage

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.


AQAD Production Strategy

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.


Renaming Files

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.


Validating File Types

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.


Validating File Size

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.


AQAD Example

Product Images:

Maximum 5 MB

Allowed:

PNG

JPEG

WEBP

Rejected:

EXE

ZIP

BAT

This protects the platform.


Storing File Information in Database

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.


Serving Uploaded Files

Suppose files are stored locally.

Express can expose them.

Example:

app.use(
   "/uploads",
   express.static("uploads")
);

Now:

/uploads/logo.png

becomes publicly accessible.


Public vs Private Files

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.


File Upload Error Handling

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.


Image Optimization

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.


Handling Product Images in AQAD

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.


Handling Vendor Documents

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.


Security Risks in File Uploads

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.


Best Practices for File Uploads

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.


Common Mistakes Beginners Make

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.


Mini Exercise

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.


Post a Comment

0 Comments