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
40: Building a Small REST API with a Scala Framework
I once worked with a developer named Marc who spent an entire weekend chasing a NullPointerException in a REST endpoint. The API documentation claimed the field asset_id was an integer, but a third-party client was sending it as a string. Because he was using a loosely typed framework, the failure didn't happen at the "edge" of the application where the request arrived; it happened deep in the service layer, three function calls in. It was a nightmare to debug because the stack trace pointed to the logic, not the input.
In Scala, we can avoid this by treating our API boundaries as type-safe contracts. Instead of manually parsing maps or relying on reflection, we use frameworks—like http4s combined with Circe—to ensure that if a request reaches your business logic, it is already validated and typed. If the input is wrong, the framework rejects it immediately with a 400 Bad Request, and your core logic never even has to deal with the mess.
Mapping Endpoints to Business Logic
When building a REST API, the biggest mistake I see is putting too much logic inside the routing definitions. You want your routes to be a thin layer of "plumbing." Let's look at a Digital Asset Registry for a game studio. We need to be able to retrieve a 3D model's metadata by its ID.
case class Asset(id: Int, name: String, polyCount: Int)
val assetRoutes = HttpRoutes.of[IO] {
case Request[IO](method = Method.GET, path = Path("/assets" / id)) =>
// We assume assetService is an injected dependency
assetService.findAsset(id.asInt).flatMap {
case Some(asset) => Ok(asset.asJson)
case None => NotFound()
}
}
Notice how the path /assets / id treats the ID as a dynamic segment. By calling id.asInt, we're leveraging the framework to handle the type conversion. If the user hits /assets/abc, the framework identifies that "abc" isn't an integer and fails fast. I've found that this declarative style makes the API much easier to audit than a chain of if/else statements checking URL strings.
Bridging the Gap with Circe JSON
A REST API is only as good as its serialization. In the Scala ecosystem, Circe is the gold standard because it doesn't rely on runtime reflection, which can be slow and brittle. Instead, it uses type classes. To make our Asset case class compatible with JSON, we define an encoder and a decoder.
import io.circe.generic.auto._
import io.circe.syntax._
// With auto-derivation, Circe looks at the case class
// and figures out the JSON mapping at compile time.
val jsonAsset = Asset(1, "Dragon_Model_HighPoly", 50000)
val jsonString = jsonAsset.asJson.noSpaces
// Result: {"id":1,"name":"Dragon_Model_HighPoly","polyCount":50000}
I usually recommend generic.auto for small projects to keep things moving, but for a production system, I'd suggest using semi-automatic derivation. It's a bit more boilerplate, but it prevents your API from breaking just because you renamed a field in your internal case class.
Launching the Blaze Server
Now that we have routes and JSON handling, we need a server to actually listen for TCP connections. In the http4s world, Blaze is a common choice. The "magic" here is that the server doesn't care about your business logic; it only cares about the HttpRoutes you've defined.
import org.http4s.server.blaze.BlazeServer
object Main extends IOApp {
def run(args: List[String]): IO[ExitCode] = {
val routes = assetRoutes.orNotFound
BlazeServer[IO]
.resource(BlazeServerBuilder[IO].bindHttp(8080, "localhost", routes))
.use(_ => IO.never) // Keep the server running
.as(ExitCode.Success)
}
}
One thing to keep in mind: .orNotFound is a crucial helper. Since HttpRoutes are essentially functions that might return None (meaning "I don't handle this path"), orNotFound ensures that any request that doesn't match your defined routes gets a clean 404 response instead of the server just hanging or crashing.
📋 Practical Task
Build a Game Character Inventory API
Your task is to extend the concepts from this lesson to create a small API for managing game character inventories. You need to implement the following:
- Define a case class
Itemwith fields:itemId: Int,itemName: String, andpowerLevel: Int. - Create a
POST` endpoint at/inventorythat accepts a JSON representation of anItemand returns a201 Createdresponse if thepowerLevelis 100 or greater, and a400 Bad Requestif it is lower. - Implement a
GET` endpoint at/inventory/statusthat returns a hardcoded JSON string:{"status": "Online"}.
Ensure you use Circe for the JSON decoding of the Item in the POST request. Your final code should be structured so that it could be passed into a BlazeServerBuilder.
There are no comments for now.