Expenn is a self-hosted expense and travel-management platform for teams.
| Surface | Stack |
|---|---|
| API | .NET 9, ASP.NET Core, EF Core, PostgreSQL, MassTransit |
| Web | Next.js, React, TypeScript, Fumadocs |
| Mobile | Expo, React Native, Fastlane |
| Infrastructure | MinIO, RabbitMQ, Docker Compose |
| Delivery | GitHub Actions, GHCR, Play Console, TestFlight |
| Path | Purpose |
|---|---|
api/Expenn.Api/ |
.NET 9 API; owns auth, business logic, and the PostgreSQL schema |
web/ |
Next.js web app |
mobile/ |
Expo SDK 54 mobile client |
docker-compose.yml |
Complete local/deployment stack |
.env.example |
The only environment template |
See ARCHITECTURE.md for service boundaries and data flow.
Docker Compose is the supported full-stack development path. It starts PostgreSQL, MinIO, RabbitMQ, the API, and the web app, and creates the object-storage bucket automatically.
cp .env.example .env
# Fill every empty value in .env before starting Compose.
docker compose up --buildOn PowerShell, use Copy-Item .env.example .env for the first command.
| Service | URL |
|---|---|
| Web | http://localhost:3000 |
| API | http://localhost:5000 |
| API health | http://localhost:5000/health |
| MinIO console | http://localhost:9001 |
| RabbitMQ console | http://localhost:15672 |
| PostgreSQL | localhost:5432 |
Useful commands:
docker compose logs -f api web
docker compose down
docker compose down --volumes # also deletes local dataAll Compose configuration lives in the ignored root .env. The tracked
.env.example lists the required variable names with intentionally empty
values; fill the local copy before starting the stack.
| Variable | Purpose |
|---|---|
APP_URL |
Public web URL and allowed browser origin |
API_URL |
Public API URL compiled into browser clients |
STORAGE_URL |
Public MinIO URL returned for uploaded files |
POSTGRES_PASSWORD |
PostgreSQL password |
MINIO_PASSWORD |
MinIO password shared with the API |
RABBITMQ_PASSWORD |
RabbitMQ password shared with the API |
JWT_SECRET |
API token-signing secret |
INTERNAL_SECRET |
Shared secret for web-to-API billing updates |
Change every password and secret before deployment. Optional email, SSO, and Paddle values are documented in api/README.md and web/README.md; inject them through the deployment platform when enabling those features.
The mobile app is interactive and runs on the host, while its backend runs in Compose:
docker compose up -d db minio minio-init rabbitmq api
cd mobile
pnpm install
pnpm startSet EXPO_PUBLIC_API_URL in your shell when a simulator or physical device cannot reach http://localhost:5000; use the computer's LAN address for a physical device. See mobile/README.md.
Use production secrets, set all three public URLs to HTTPS endpoints, and run:
docker compose up -d --buildPlace a TLS-terminating reverse proxy in front of web, API, and object storage. Do not commit the production .env or expose PostgreSQL, RabbitMQ, or MinIO administration ports publicly; restrict those published ports in the deployment platform or a production override.
| Workflow | Purpose |
|---|---|
api-ci.yml |
Restore/build the .NET API, validate Compose, build/publish the API image |
web-ci.yml |
Type-check, lint, build, publish, and smoke-test the web image |
mobile-*-ci.yml |
Type-check and produce signed development artifacts |
mobile-*-release.yml |
Publish release builds to Play Console or TestFlight |
release.yml |
Create a GitHub release from a vX.Y.Z tag |
Container images live at ghcr.io/usmhic/expenn-api and
ghcr.io/usmhic/expenn-web. Mobile credentials remain in GitHub Secrets; see
the mobile guide.
- Architecture
- API guide
- Web guide
- Mobile guide
- Engineering standards
- Coding-agent guide
- Security policy
Issues and pull requests are welcome. Start with CONTRIBUTING.md and follow the Code of Conduct.