A URL shortening API. The product is an API, not an application with a user interface — every operation (registration, key management, link CRUD, statistics) is reachable over HTTP.
The project is at an early stage: the development environment, the Symfony skeleton and the quality tooling are in place. The domain is not implemented yet.
Docker and Docker Compose. Nothing else — PHP, Composer and the databases all run in containers.
make init # create .env from .env.example
make up # nginx, php, postgres, postgres-analytics, redis, rabbitmq, mailpit
make db-test-create # test databases for both connectionsApplication: http://localhost:8000. Mailpit inbox: http://localhost:8025. RabbitMQ management UI: http://localhost:15672.
Workers and cron live behind a separate compose profile, so the HTTP stack can be started without them:
make workers-upRun make help for the full list of targets.
| Service | Host port | Notes |
|---|---|---|
| nginx | 8000 | public listener |
| nginx | — | internal listener on 8081, deliberately not published |
| postgres | 5432 | default database: accounts, links, moderation |
| postgres-analytics | 5433 | analytics database: clicks and rollups |
| redis | 6379 | cache, click buffer, rate limiting |
| rabbitmq | 5672 / 15672 | business events |
| mailpit | 8025 | captures outgoing mail |
Internal endpoints (/health/status, /metrics, /fpm-status) are served by the listener on
port 8081, which is not mapped to the host. On the public listener they return 404. Reach them
with make metrics, make health-status and make fpm-status.
PHP 8.5, Symfony 8.1, PostgreSQL 17 (two databases, two entity managers), Redis 7, RabbitMQ, nginx + PHP-FPM. Tests: PHPUnit 12. Analysis: PHPStan at level max, PHP-CS-Fixer, Deptrac.
DDD + CQRS. The code is split into bounded contexts under src/, each with four layers:
src/
Shared/ kernel shared between contexts
Identity/ accounts, authentication, plans and quotas
UrlShortening/ creating and managing links
Redirection/ serving redirects (hot path)
Analytics/ click counting and reporting
Safety/ abuse protection
The boundaries — both between layers and between contexts — are enforced by Deptrac through
deptrac-layers.yaml and deptrac-contexts.yaml, not merely documented.
make cs # PHP-CS-Fixer
make stan # PHPStan at level max
make deptrac # layer and bounded context boundaries
make test # every test suite
make check # all four in sequenceTest suites: tests/Unit (no I/O), tests/Integration (real Postgres and Redis, each test
wrapped in a transaction that is rolled back), tests/Functional (end to end through the HTTP
kernel).
Two independent sets, because there are two databases:
make migrate # default database
make migrate-analytics # analytics database
make migrate-status # status of bothMigrations never run automatically when a container starts — that is a separate deployment step.
