Create
POST /api/links validates a URL, generates a short code, applies optional expiry, and persists the link.
Case study · Java REST API
A Spring Boot REST API for creating short links, resolving them through HTTP 302 redirects, tracking clicks, managing expiration and deactivation, and persisting link state in PostgreSQL.
The problem
The service needs to create unique short codes, resolve them quickly, reject expired or inactive links, update analytics, and keep lifecycle rules separate from HTTP handling.
REST API flow
POST /api/links validates a URL, generates a short code, applies optional expiry, and persists the link.
GET /{code} resolves the link and returns an HTTP 302 redirect when the short link is valid and active.
Separate endpoints return analytics and manually deactivate links, while a scheduler handles expired active links.
Short-code generation
The generator uses Java SecureRandom and a Base62-style alphabet containing 0-9, A-Z, and a-z. Each code is seven characters long.
Each position is selected using Java's SecureRandom rather than a sequential counter.
Before persistence, the service checks whether the generated code already exists and retries when a collision is found.
Only HTTP/HTTPS URLs with a valid host are accepted before a short link is created.
Persistence & analytics
The JPA entity defines a unique database index on short_code, which is the key lookup used by redirect requests.
Resolution runs inside a Spring transaction while click count and last-access time are updated.
An optional expiry timestamp is stored per link, and a Spring Scheduler task periodically marks expired active links inactive.
Manual deactivation is also supported through the REST API. Once a link is inactive or expired, redirect attempts are rejected.
Testing & delivery
The repository uses JUnit 5 and Mockito for service tests, including short-code generation/persistence and expired-link rejection. Docker and Docker Compose package the Spring Boot service with PostgreSQL, and the Docker build runs the Maven test/package lifecycle before producing the runtime image.
Concurrency & future scalability
The current implementation reads the entity, increments clickCount, and writes the changed state inside a transaction. Under heavier concurrent redirect traffic, a simple read-modify-write counter can lose increments unless the update strategy is made concurrency-safe. A production version could use an atomic database increment, move hot redirect lookups behind a cache, and process analytics asynchronously so redirects are not coupled to analytics writes.
Possible future improvements include a cache layer such as Redis, rate limiting, migrations, expanded integration tests, and asynchronous analytics. None of those are implemented in the current repository.