Skip to content

luksow/madrileno

Repository files navigation

madrileno

A Scala 3 backend template. Wine auctions are the showcase domain.

If you're using this as a template, run the rename script first — it swaps madrileno for your project name + package and removes the auction demo:

./scripts/init-project.scala <project-name>

See docs/scripts.md for what it does and what else lives under scripts/.

Quick start

You'll need:

  • JDK 21 (Temurin recommended)
  • sbt 1.12+ (sbt --version to check)
  • Docker with docker compose

1. Boot the dev stack

docker compose up -d

That brings up three services on non-standard host ports so they don't clash with anything you already have running:

Service Image Host port(s) Login
Postgres postgres:latest 55432 → 5432 postgres / postgres
Mailpit axllent/mailpit:latest 51025 (SMTP), 58025 (UI)
OpenObserve public.ecr.aws/zinclabs/openobserve:latest 55080 (UI + OTLP HTTP) root@example.com / Complexpass#123

State persists across restarts in named volumes. To wipe and start clean:

docker compose down -v
docker compose up -d

2. Configure env

cp .env.sample .env

The sample is wired against the docker-compose ports above — including a working Authorization header for OpenObserve so OTLP traces show up in the UI immediately.

JWT_SECRET is fine as-is for local dev — change it when you don't want strangers to be able to forge tokens. External auth providers (FIREBASE_PROJECT_ID, OIDC_*) ship empty; the app boots fine without them and the dev login (POST /v1/auth/dev) is enabled by default via DEV_AUTH_ENABLED=true.

3. Apply database migrations

Two ways:

sbt "runMain madrileno.main.MigrateMain"   # recommended
sbt flywayMigrate

runMain madrileno.main.MigrateMain is the app's own IOApp — same as bin/migrate-main in the Docker image — so it reads application.conf with .env injected by sbt-dotenv. Works out of the box on the default .env.

sbt flywayMigrate is the sbt-flyway plugin task. It evaluates sys.env at build-load time — before sbt-dotenv injects .env — so it only works if your shell already has PG_HOST / PG_PORT / PG_DATABASE / PG_USER / PG_PASSWORD exported. The other plugin tasks (flywayInfo / flywayValidate / flywayClean) have the same constraint but are handy for inspection regardless.

Run a migration every time you add one under src/main/resources/db/migration/.

4. Run the app

The recommended workflow is one long-lived sbt session in interactive mode:

sbt

Inside the sbt shell:

> ~reStart

~reStart watches sources and restarts the app on every save (compile-on-save). Plain reStart runs it once. The app comes up on http://localhost:9000 (override with PORT in .env).

Quick smoke test from another terminal:

curl http://localhost:9000/v1/health-check

5. Look around

Things that catch people out

  • sbt caches .env at JVM startup. If you change .env, exit sbt and start it again. Same goes for the long-lived sbt server (~/.sbt/1.0/server/...).
  • docker compose down keeps volumes; docker compose down -v wipes them. Wipe when you want a clean PG, change OpenObserve credentials, or cycle a corrupted state.
  • Tests don't use the docker-compose stack. Testcontainers spins up its own. Don't worry about polluting your dev DB during a test run.
  • Migrations don't run automatically when the app starts. Run sbt flywayMigrate after adding one or after wiping volumes.
  • OpenObserve creates OTLP streams on first ingest. If the Traces tab is empty right after boot, hit the app a few times, refresh, give it a moment.

Documentation

Reference material lives in docs/. For day one, read in this order:

The docs/README.md lists everything else by topic — stack (auth, scheduler, observability…), conventions (domain modeling, sealed monad, error handling), operations (configuration, deployment).

About

A Scala 3 backend template - http4s + Skunk + cats-effect, with a wine-auction module as the worked example.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages