A small URL shortener REST API built with ASP.NET Core 8, EF Core, and PostgreSQL. Post a long URL, get back an 8-character token; request the token and get a 302 redirect to the original. Links expire after 30 days.
| Runtime | .NET 8 (net8.0) |
| Web | ASP.NET Core, attribute-routed controllers |
| Data | EF Core 8.0.30 + Npgsql 8.0.11, PostgreSQL |
| Tests | xUnit + Moq + EF Core InMemory — 13 tests |
| Method | Route | Body / Param | Response |
|---|---|---|---|
POST |
/api/urls |
{ "originalUrl": "https://example.com" } |
200 { shortUrlToken, fullShortUrl } · 400 on empty or malformed URL |
GET |
/api/urls/{shortUrl} |
short token in path | 302 redirect to the original · 404 if unknown or expired |
Posting a URL that already exists returns the existing token rather than minting a second one.
You need the .NET 8 SDK and a reachable PostgreSQL instance.
git clone https://github.com/Ar4gornn/UrlShortenerAPI.git
cd UrlShortenerAPIThe connection string is not in source control. Create
UrlShortenerAPI/appsettings.Development.json (already gitignored):
{
"ConnectionStrings": {
"UrlShortenerDB": "Host=localhost;Port=5432;Database=urlshortener;Username=postgres;Password=your-password"
}
}Apply the migrations and run:
dotnet ef database update --project UrlShortenerAPI/UrlShortenerAPI.csproj
dotnet run --project UrlShortenerAPI/UrlShortenerAPI.csprojThe API listens on http://localhost:5251. Try it:
curl -X POST http://localhost:5251/api/urls \
-H "Content-Type: application/json" \
-d '{"originalUrl":"https://learn.microsoft.com/aspnet/core"}'
curl -i http://localhost:5251/api/urls/<token>Run the tests:
dotnet testA thin three-layer slice. UrlsController handles HTTP concerns only — model validation and
mapping exceptions to status codes. UrlShortenerService holds the business rules: URL well-formedness
via Uri.IsWellFormedUriString, deduplication against existing rows, token generation, and the
expiry check on read. UrlShortenerContext is the EF Core boundary, with three migrations tracked
in Migrations/. The service is registered scoped and injected through IUrlShortenerService, so
the controller tests mock the interface and the service tests run against an in-memory provider.
Tokens are the first 8 characters of a Guid, and expiry is 30 days from creation.
Honest list — these are real, and they are the roadmap.
- Token collisions are not handled. Tokens are truncated GUIDs with no uniqueness check before
insert, and the read path uses
SingleOrDefaultAsync, which throws if a duplicate ever lands. fullShortUrlhardcodeshttp://localhost:5251. The response is wrong anywhere but a local dev machine.- The
Dockerfiledoes not work. It builds on the .NET 8 SDK image but runs on the ASP.NET 7.0 runtime image, so the publishednet8.0assembly will not start. Fix before trusting it. - CORS is
AllowAnyOrigin+ any method + any header. Fine for local development, not for a public deployment. - No Swagger UI.
Microsoft.AspNetCore.OpenApiis referenced but never wired up inProgram.cs. AppConstants.LinkExpirationDaysand the privateGenerateUniqueGuidToken()helper are both dead code — the 30-day window is inlined in the service instead.
- Collision-safe token generation (retry loop or a counter-based encoding), plus a unique index
- Base URL from configuration instead of a hardcoded localhost string
- Fix the Dockerfile runtime tag and add a
docker composewith PostgreSQL - Swagger/OpenAPI
- Tighten CORS to a configured origin list
- Hit counter per link
MIT — see LICENSE.