A simple URL shortener
It provides:
- a REST API for creating, redirecting, listing, and deleting shortened URLs
- file-based persistence using H2
- a minimal decoupled frontend for interacting with the API
- automated tests
- Docker support for running the full application locally
Screencast.from.2026-03-17.13-43-47.webm
- Java 17
- Spring Boot 3.5.11
- Maven
- Spring Web
- Spring Validation
- Spring Data JPA
- H2 (file-based)
- Lombok
- Node.js
- Express
- Static HTML / CSS / JavaScript
The API follows the provided openapi.yaml.
Creates a shortened URL.
Request body:
{
"fullUrl": "https://example.com/very/long/url",
"customAlias": "my-custom-alias" // optional
}Response (201 Created):
{
"shortUrl": "http://localhost:8080/my-custom-alias" // or a randomly generated alias
}Redirects to the original URL.
Response:
302 Foundif alias exists404 Not Foundif alias does not exist
Deletes a shortened URL mapping from persistence.
Response:
204 No Contentif deleted404 Not Foundif alias does not exist
Returns all shortened URLs.
Response (200 OK):
[
{
"alias": "my-custom-alias",
"fullUrl": "https://example.com/very/long/url",
"shortUrl": "http://localhost:8080/my-custom-alias"
},
{
"alias": "kqmdz-plnvrtaeuw",
"fullUrl": "https://example.org/another/long/url",
"shortUrl": "http://localhost:8080/kqmdz-plnvrtaeuw"
}
]shortUrlis not persisted- persisted fields are:
aliasfullUrl
shortUrlis derived asbase-url + "/" + alias- base-url is read from the environment by the
APP_BASE_URLor is the defaulthttp://localhost:8080/ - example:
fullUrl = https://example.com/very/long/urlalias = my-custom-aliasshortUrl = http://localhost:8080/my-custom-alias
Custom aliases are optional and validated as follows:
- lowercase letters and hyphens only
- must start and end with a lowercase letter
- no consecutive hyphens
- max length: 16 characters
- DTO validation handles basic field validation
- API layer request validation checks that
fullUrlis a valid absolutehttporhttpsURL - business rules such as alias uniqueness are handled in the service layer
- API errors are returned as JSON error responses
Example error response:
{
"error": "Alias already exists: my-custom-alias"
}From the repository root:
docker-compose up --buildServices:
- frontend:
http://localhost:3000 - backend:
http://localhost:8080
The backend uses a Docker volume for H2 file persistence.
To stop containers:
docker-compose downTo stop containers and remove the persisted database volume:
docker-compose down -v- Java 17
- Maven
- Node.js and npm
From backend/url-shortener-api/:
./mvnw spring-boot:runcan be run at the same location by:
./mvnw testBackend runs on:
http://localhost:8080
From the frontend/ directory:
npm ci
npm startFrontend runs on:
http://localhost:3000
and can be accessed by opening http://localhost:3000 in a browser
A minimal decoupled frontend is included, it supports:
- creating short URLs
- listing saved URL mappings
- deleting saved URL mappings
- showing API success and error messages
The frontend is served separately, the browser essentially retrieves the static web pages from the frontend docker container and calls the API directly from the browser.
- aliases are unique across all saved URL mappings.
- a url can have many aliases.
- H2 file-based persistence being sufficient for this exercise
This submission intentionally keeps the solution simple and focused on the requirements:
- manual implementation from the provided OpenAPI contract
- backend-first approach
- minimal decoupled frontend
- simple Docker setup
Time spent: approximately ~15 hours across several sessions.
curl -i -X POST http://localhost:8080/shorten \
-H "Content-Type: application/json" \
-d '{
"fullUrl": "https://example.com/very/long/url",
"customAlias": "my-custom-alias"
}'curl -i http://localhost:8080/my-custom-aliascurl -i http://localhost:8080/urlscurl -i -X DELETE http://localhost:8080/my-custom-alias