Scala
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Scala
-
Section 4: Functional Scala
-
Section 5: Collections in Depth
-
Section 6: Type System
-
Section 7: Concurrency and Ecosystem
-
Section 8: Practical Projects
-
Section 9: Interview Practice
-
Section 10: Data Structures and Algorithms in Scala
-
Section 11: More Practice Exercises
-
Section 12: Advanced Functional Patterns
-
Section 13: More Ecosystem
-
Section 14: Scala Collections Library Deep Dive
-
Section 15: Scala Standard Library Deep Dive
-
Section 16: Akka Ecosystem Deep Dive
-
Section 17: Cats and Cats Effect Deep Dive
-
Section 18: Play Framework Deep Dive
-
Section 19: Apache Spark with Scala Deep Dive
-
Section 20: Scala Build Tools Deep Dive
-
Section 21: Scala 3 Specific Features
-
90: Union and Intersection Types
-
Section 22: Scala Testing Deep Dive
-
Section 23: Functional Domain Modeling
-
Section 24: More Data Structures and Algorithms in Scala
-
Section 25: Scala for Data Engineering
-
Section 26: More Practical Projects
-
Section 27: More Interview and Review
-
Section 28: ZIO Ecosystem Deep Dive
-
Section 29: Scala for Machine Learning
-
Section 30: Scala Microservices Architecture
-
Section 31: Scala Type System Deep Dive
-
Section 32: More Practice and Drills
-
Section 33: Scala Performance Deep Dive
-
Section 34: Scala Ecosystem Tooling
-
Section 35: Scala for Reactive Systems
-
Section 36: More Real-World Case Studies
-
Section 37: Scala for Financial Systems
-
Section 38: Scala GraphQL and gRPC
-
Section 39: More Final Projects
-
Section 40: More Interview and Final Review
-
Section 41: Scala for Streaming Data
-
Section 42: Scala Security Practices
-
Section 43: More Language Deep Dive
-
Section 44: Scala Command-Line Tools
-
Section 45: Scala Documentation and Style
-
Section 46: Scala Dependency Management
-
Section 47: More Practical Backend Patterns
-
Section 48: Scala for Event-Driven Architecture
-
Section 49: More Practice Drills Round 2
-
Section 50: Scala Compiler Deep Dive
-
Section 51: Scala for Web Frontends
-
Section 52: More Data Engineering Practice
-
Section 53: Scala Observability
-
Section 54: More Advanced Practice Projects
-
Section 55: Scala for Legacy Java Integration
-
Section 56: More Testing Practice
-
Section 57: Final Mastery Review
-
Section 58: Scala History and Ecosystem Context
-
Section 59: More Concurrency Patterns
-
Section 60: Scala for Configuration Management
-
Section 61: More Domain Modeling Practice
189: Request Validation Middleware
I've seen this pattern a hundred times in production codebases. A developer starts with one or two endpoints, and they handle validation right there in the route handler. It seems fine at first. But then the app grows to fifty endpoints, and suddenly the "business logic" is buried under a mountain of if-statements checking for nulls, empty strings, or negative numbers.
case class TransferRequest(fromAccount: String, toAccount: String, amount: Double)
// This is the "manual" way that eventually breaks everything
def handleTransfer(req: Request[TransferRequest]): Response = {
val body = req.body
if (body.fromAccount.isEmpty) {
return Response.BadRequest("From account is required")
}
if (body.toAccount.isEmpty) {
return Response.BadRequest("To account is required")
}
if (body.amount <= 0) {
return Response.BadRequest("Amount must be positive")
}
// Finally, the actual logic
bankingService.transfer(body.fromAccount, body.toAccount, body.amount)
Response.Ok("Transfer successful")
}
The Repetition Trap
The problem here isn't that the code is "wrong"—it works. The problem is that this logic is now coupled to the handler. If you add a handleWithdrawal or a handleDeposit endpoint, you're going to copy-paste those if blocks. Eventually, someone will forget to check if the amount is positive in one of those endpoints, and you'll end up with a bug where users can "withdraw" negative money to effectively deposit it into their account. I've literally seen that happen in a fintech app I consulted for.
We need to move this validation "up the chain" so the handler only ever receives a request that is already guaranteed to be valid. In Scala, we can do this using middleware—a function that wraps our handler.
Short-Circuiting with Validation Middleware
Instead of letting the request hit the handler and then checking its validity, we create a middleware layer that intercepts the request. If the validation fails, the middleware "short-circuits" the request and returns a BadRequest immediately. The handler never even knows the request existed.
case class ValidatedRequest[T](data: T)
// A generic validation middleware
def withValidation[T](validator: T => Either[String, T])(handler: Request[ValidatedRequest[T]] => Response): Request[T] => Response = {
request => {
validator(request.body) match {
case Left(error) =>
Response.BadRequest(error)
case Right(validData) =>
handler(Request(ValidatedRequest(validData)))
}
}
}
// Now we define a specific validator for our TransferRequest
val transferValidator: TransferRequest => Either[String, TransferRequest] = req => {
if (req.fromAccount.isEmpty) Left("From account is required")
else if (req.toAccount.isEmpty) Left("To account is required")
else if (req.amount <= 0) Left("Amount must be positive")
else Right(req)
}
// The handler is now clean. It only deals with the "Happy Path".
def handleTransfer(req: Request[ValidatedRequest[TransferRequest]]): Response = {
val body = req.body.data
bankingService.transfer(body.fromAccount, body.toAccount, body.amount)
Response.Ok("Transfer successful")
}
// Wiring it together
val route = withValidation(transferValidator)(handleTransfer)
Notice how handleTransfer now takes a ValidatedRequest instead of a raw TransferRequest. This is a powerful trick. By changing the type, I'm using the Scala compiler to prove that this handler cannot be called unless the validation middleware has already run. You can't accidentally skip the validation because the types won't match.
I prefer this approach because it separates what the request should look like from what the system should do with it. Your handlers stay lean, and your validation logic is centralized and reusable. If the business decides that amount must now be at least 1.00, you change it in one validator, and every single endpoint using that middleware is instantly updated.
📋 Practical Task
Build a Multi-Header Security and Versioning Middleware
You are building a public API that requires two things before any request is processed: a valid X-API-Key (which should be "secret-key-123" for this exercise) and an X-API-Version header (which must be exactly "v2").
Create a middleware function called withSecurityHeaders. This middleware should check the headers of an incoming Request:
- If
X-API-Keyis missing or incorrect, return aResponse.Unauthorized("Invalid API Key"). - If
X-API-Versionis missing or not "v2", return aResponse.BadRequest("Unsupported API Version"). - If both are present and correct, pass the request through to the inner handler.
Test your middleware by wrapping a simple "Hello World" handler. Ensure that requests with missing or incorrect headers are rejected before they ever reach the handler logic.
There are no comments for now.