A railway infrastructure management application for tracking segments and incidents across a rail network.
Built as a technical portfolio project using a modern full-stack: Angular 21 on the frontend and Spring Boot 3 on the backend.
- Manage track segments — lines, types, operational status, maintenance history
- Track incidents — signal failures, geometry deviations, equipment malfunctions
- JWT-based authentication with role support
- Seeded with real Swiss railway data (Bern–Olten, Lausanne–Yverdon, Zürich Depot)
| Layer | Technology |
|---|---|
| Frontend | Angular 21, Angular Material, standalone components |
| Backend | Spring Boot 3.5, Java 21, Spring Security + JWT |
| Database | H2 in-memory (development) |
| Secrets | HashiCorp Vault (spring-cloud-vault-config) |
| Build | Maven (backend), npm (frontend) |
| CI | GitHub Actions |
railtrack-manager/
├── backend/ # Spring Boot REST API
└── frontend/ # Angular SPA
Requirements: Java 21, Node.js 18+, Maven, Docker (for Vault)
The backend reads jwt.secret and jwt.expiration from a HashiCorp Vault dev server —
they are no longer stored in application.properties. Vault must be up and populated
before the backend starts, or Spring Boot fails on spring.config.import=vault://.
# 1. Start a Vault dev server (in-memory, not for production)
docker run -d --name railtrack-vault \
-e "SKIP_SETCAP=true" \
-e "VAULT_DEV_ROOT_TOKEN_ID=mi-token-dev" \
-e "VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200" \
-p 8200:8200 hashicorp/vault:latest
# 2. Seed the JWT secrets Spring Cloud Vault expects at secret/railtrack-backend
docker exec -e VAULT_ADDR='http://127.0.0.1:8200' -e VAULT_TOKEN='mi-token-dev' \
railtrack-vault vault kv put secret/railtrack-backend \
jwt.secret=404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970 \
jwt.expiration=86400000
# 3. Start the backend (port 8080)
cd backend
./mvnw spring-boot:run
# 4. Start the frontend (port 4200)
cd frontend
npm install
npm startSKIP_SETCAP=true works around a CAP_SETFCAP permission error the official Vault
image hits under Docker Desktop on Windows (WSL2 backend) — harmless in dev mode,
where mlock isn't required anyway.
Vault's dev server is in-memory: all secrets are lost when the container stops.
Re-run step 2 any time you recreate the container. The token (mi-token-dev) and
URI (http://localhost:8200) must match spring.cloud.vault.token /
spring.cloud.vault.uri in backend/src/main/resources/application.properties.
Open http://localhost:4200 — you will be redirected to the login page.
No user is seeded. DataLoader only seeds track segments and incidents, not users —
the H2 database starts empty of accounts every time the backend restarts. You must create
one yourself before you can log in, either:
-
through the UI's registration form, or
-
by calling the API directly:
curl -X POST http://localhost:8080/api/auth/register \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"admin123"}'
Registration always assigns the USER role — there is no endpoint to create an ADMIN.
Since H2 is in-memory, this user is lost on every backend restart and must be recreated.
The backend exposes a REST API at http://localhost:8080/api.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/auth/register |
No | Create account |
| POST | /api/auth/login |
No | Get JWT token |
| GET | /api/tracks |
Yes | List track segments |
| POST | /api/tracks |
Yes | Create track segment |
| PUT | /api/tracks/{id} |
Yes | Update track segment |
| DELETE | /api/tracks/{id} |
Yes | Delete track segment |
| GET | /api/incidents |
Yes | List incidents |
| POST | /api/incidents |
Yes | Create incident |
| PUT | /api/incidents/{id} |
Yes | Update incident |
| DELETE | /api/incidents/{id} |
Yes | Delete incident |
H2 console available at http://localhost:8080/h2-console (JDBC URL: jdbc:h2:mem:railtrackdb).
.github/workflows/ci.yml runs on every push and pull request against main:
- Backend —
./mvnw verify(Java 21) — compiles and runs the JUnit test suite. - Frontend —
npm ci,npm test -- --watch=false(Vitest) andnpm run build.
Vault is not part of CI: the backend tests use their own test configuration and don't
need spring.config.import=vault:// to run, so no Vault service is started in the workflow.