HTTP Catalog API
Build a REST API for a product catalog in Scala using http4s and Cats Effect, the purely functional way of writing web services. The API supports listing products with filters (category, price range, search text), sorting and pagination, getting one product, and creating and updating products with JSON input that is validated field by field, returning every error at once instead of only the first. Data is stored in PostgreSQL through Doobie. You'll learn functional effects with Cats Effect's IO, routing with http4s, JSON with Circe, collecting validation errors with Cats' Validated, database access with Doobie, and testing routes without starting a server.
- Estimate
- ~13.5h
- Steps
- 7
- Completed by
- 0
- Proposed by
- codeseed.app
Cats Effect · http4s
Project roadmap
- 01
Define the API
~1.5hWrite API.md: a product has an id, sku (unique), name, description, category, price in cents and stock. List the endpoints GET /products (filters: category, minPrice, maxPrice, q; sort: price or name; page and pageSize with a maximum of 100), GET /products/{id}, POST and PUT, with example requests, responses and the error format (a list of field errors). Done when: API.md shows every endpoint with examples.
- 02
Set up the server
~2hCreate an sbt project with http4s (Ember server), Circe and Cats Effect. Write an IOApp that starts the server on a configurable port with a GET /health route, and define Product and the request types as case classes with Circe encoders and decoders. Done when: the server starts, and /health returns 200.
- 03
Store products with Doobie
~2.5hAdd Doobie with a HikariCP transactor and PostgreSQL. Write the table with a migration (Flyway), and a ProductRepository trait with a Doobie implementation: find by id, insert, update and a list query built from optional filters with Fragments (for example Fragments.whereAndOpt), plus a count for pagination. Seed about 100 products. Done when: repository methods return the right rows in a quick test.
- 04
Build the routes
~2.5hImplement the routes with the http4s DSL, parsing query parameters with query param matchers and returning items with page, pageSize and total. Return 404 for unknown ids and 409 when a SKU already exists (caught from the database's unique violation). Keep the routes depending on the repository trait, not on Doobie. Done when: every endpoint in API.md works with curl.
- 05
Validate input
~2hValidate create and update requests with Cats' ValidatedNel: the name is 1 to 200 characters, the price is greater than 0, stock is not negative, the SKU matches a pattern, and the category is from the allowed list. Collect all errors and return 400 with the list. Also validate query parameters (pageSize over 100, minPrice above maxPrice). Done when: a request with three invalid fields gets all three errors in one response.
- 06
Handle failures
~1hAdd middleware for request logging and a global error handler that returns a 500 JSON error without internal details. Set a timeout on database calls, and return 503 when the database is unreachable. Done when: stopping the database returns clean 503 responses and the server keeps running.
- 07
Test it
~2hWrite tests with munit-cats-effect (or weaver): route tests calling routes.orNotFound.run with an in-memory repository (filters, pagination, validation errors, 404 and 409), and repository tests against a real PostgreSQL with Testcontainers. Done when: sbt test passes.
Resources
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack