Skip to main content

Command Palette

Search for a command to run...

Validations and Transformations for Backend Engineers

Updated
•8 min read•View as Markdown
Validations and Transformations for Backend Engineers
A
Just an engineer trying to build better software. This blog is my engineering journal, where I share backend concepts, system design, AI experiments, and the lessons I pick up while building real products.

So far in this series, we've talked about HTTP, routing, serialization, authentication, and how requests eventually reach our backend.

But there's one assumption we've quietly been making:

The data coming from the client is correct.

And that's definitely not something a backend should assume.

Imagine our API expects:

{
  "name": "Obi-Wan Kenobi",
  "age": 38
}

but receives:

{
  "name": 66,
  "age": "hello there"
}

That's still perfectly valid JSON. The server can receive it. The JSON parser can deserialize it. But from our application's perspective, the data makes absolutely no sense.

That's where validation and transformation come into play.


What is Validation?

Validation is the process of checking whether incoming data follows the rules our application expects.

If any of these conditions fail, the request should ideally be rejected before it reaches our business logic or database.

This applies to almost anything coming from a client:

  • Request body

  • Query parameters

  • Path parameters

  • Headers

  • Cookies

  • Webhook payloads

If it's coming from outside your application, treat it as untrusted.


What is Transformation?

Validation asks:

Is this data acceptable?

Transformation asks:

Can I convert this data into the format my application expects?

Consider pagination:

GET /api/orders?page=2&limit=20

The values in the URL arrive as textual data, so your application may receive something equivalent to:

page = "2"
limit = "20"

But your pagination logic wants numbers:

page = 2
limit = 20

So we transform them.

Transformation can also be used for normalization:

"  leia@example.com  "
        ↓
Trim
        ↓
"leia@example.com"

So the flow often becomes:

Raw Input
    ↓
Transform
    ↓
Validate
    ↓
Application

The exact order can vary depending on the use case.


Where Does Validation Happen?

Let's connect this to the backend architecture we've been using throughout this series.

A simplified application might look like:

Request
   ↓
Router
   ↓
Controller
   ↓
Service
   ↓
Repository
   ↓
Database

Request validation usually happens near the entry point of the application, before untrusted input reaches the service layer.

Depending on the framework, this might be implemented through:

  • Middleware

  • Schema validators

  • DTOs

  • Request parsers

  • Controller validation

Conceptually:

Route Matched
     ↓
Request Data
     ↓
Validation / Transformation
     ↓
Controller
     ↓
Service
     ↓
Repository

The important part isn't exactly which file contains the validator.

The important part is:

Validate external input before allowing it into your business logic.


Why Backend Validation Matters

Suppose our database expects:

name → TEXT

but the client sends:

{
  "name": 66
}

Without validation, this data might travel through the controller, service, and repository before eventually being rejected by the database or database driver.

If that error isn't handled properly, the client could receive:

500 Internal Server Error

But the server isn't really the problem.

The client sent invalid data.

Instead, we want:

Client
  ↓
Validation
  ↓
Invalid Input
  ↓
400 Bad Request

or, depending on your API conventions:

422 Unprocessable Content

The request stops there.

No unnecessary database call.

No business logic executed with invalid input.

Much cleaner.


The Validation Pipeline

A simple validation pipeline usually checks data in stages.

1. Does the value exist?

Suppose username is required.

{}

We can immediately reject the request.

2. Is it the correct type?

{
  "username": true
}

The field exists, but we expected a string.

Reject it.

3. Does it satisfy our constraints?

{
  "username": "a"
}

It's a string, but maybe usernames must contain at least three characters.

So the mental model becomes:

Exists?
 
Correct Type?
  
Correct Format?
   
Meets Constraints?
   
Accepted

The Main Types of Validation

There are four useful categories to keep in mind.

1. Type Validation

This checks whether the value has the expected data type.

For example:

{
  "name": "Leia",
  "age": 19,
  "active": true
}

might require:

name   → string
age    → number
active → boolean

If age comes in as:

{
  "age": "nineteen"
}

it fails.


2. Syntactic Validation

Sometimes the type is correct, but the format isn't.

For example:

{
  "email": "definitely-not-an-email"
}

It's technically a string.

But it doesn't match the structure expected for an email address.

Other common examples include:

  • Dates

  • UUIDs

  • Phone numbers

  • URLs

  • IP addresses

So syntactic validation asks:

Does this value follow the expected structure?


3. Semantic Validation

This checks whether the data actually makes sense.

For example:

{
  "dateOfBirth": "2099-01-01"
}

The value may be:

String? ✅
Correct date format? ✅

But the user hasn't been born yet.

That's semantically invalid.

Similarly:

{
  "age": 430
}

might technically pass our type checks.

Semantic validation deals with logical meaning.


4. Dependent Validation

Sometimes the validity of one field depends on another.

For example:

{
  "password": "helloThere123",
  "confirmPassword": "helloThere123"
}

Both values might individually be valid strings.

But we also need:

password === confirmPassword

Another example could be:

If married = true
partnerName must exist

This is also called cross-field validation.


Transformation in Practice

Let's return to our pagination example:

GET /api/jedi?page=2&limit=20

Our application might receive:

page = "2"
limit = "20"

So we transform them:

"2"  → 2
"20" → 20

Then validate:

Is integer?
     
Greater than 0?
     
Within allowed limit?

This gives us:

Raw Input
    
Transform
    
Validate
    
Normalized Data

Transformation can also remove unnecessary whitespace or normalize values where doing so is intentional.

But there's an important rule here:

Don't silently modify arbitrary user data.

For example, transforming an email may be acceptable depending on your system.

Transforming someone's password absolutely is not.


Frontend vs Backend Validation

A very common question is:

If my frontend already validates everything, why validate it again?

Because the frontend is not a security boundary.

Imagine your React form only accepts ages between 18 and 100.

Great.

But someone can completely ignore your React app and send:

POST /api/users

directly using:

  • Postman

  • Insomnia

  • curl

  • Custom scripts

with:

{
  "age": -500
}

Your frontend validation never gets involved.

So the distinction is simple:

Frontend Validation
        
Better UX

Backend Validation
        
Security + Data Integrity

Frontend validation helps legitimate users.

Backend validation protects the application.

You usually want both.


Request Validation vs Business Validation

There's one final distinction worth understanding.

Suppose the client sends:

{
  "accountId": 123,
  "amount": 100
}

Request validation can verify:

accountId → integer ✅
amount → number ✅
amount > 0 ✅

Everything looks fine.

But what if account 123 only contains ₹50?

Now the request is structurally valid, but the operation violates a business rule.

That check belongs deeper inside the application:

Request Validation
        
Service Layer
        
Does account have enough balance?
        
Yes / No

This gives us multiple layers of protection:

Request Validation
→ Is the input shaped correctly?

Business Validation
→ Is this operation logically allowed?

Database Constraints
→ Can invalid state still be prevented?

Your database might still enforce constraints such as:

  • NOT NULL

  • UNIQUE

  • FOREIGN KEY

  • CHECK

Each layer protects something different.


Putting It All Together

A typical request might flow like this:

Incoming Request
      ↓
Route Matched
      ↓
Parse Input
      ↓
Transform
      ↓
Validate
      ↓
Controller
      ↓
Service
      ↓
Repository
      ↓
Database

If validation fails:

Invalid Input
     ↓
400 / 422

If it succeeds:

Valid Input
     ↓
Business Logic

That's basically the whole idea.

Validation ensures that the data entering our application satisfies the rules we expect.

Transformation converts that data into a representation our application can comfortably work with.

And the most important principle behind both is:

Never trust the client.

Because no matter how strict your frontend form is, someone somewhere has curl, Postman, and enough curiosity to find out what happens when they send:

{
  "quantity": "execute order 66"
}

Your backend should already know the answer.

Thanks for reading.