How to Build a URL Shortener With .NET: A Beginner's Step-by-Step Guide
A friendly, step-by-step guide to building a URL shortener in .NET 10 using minimal APIs and EF Core. Learn short codes, redirects, and storage.
Think about a long address on an envelope. Imagine writing out your friend's full address every single time: house number, lane, area, city, pin code, and landmark. It is long and easy to get wrong. Now imagine you both agree on a short nickname like "Raju's place." You say the nickname, and the postman already knows the full address. Short to say, but it still reaches the right door.
A URL shortener does exactly this for web links. You give it a very long link. It gives you a short one. When someone opens the short link, the service quietly looks up the long link and sends them there. Sites like bit.ly do this millions of times a day.
In this guide you will build your own URL shortener in .NET 10 using minimal APIs and Entity Framework Core. We will go slowly, one small step at a time. By the end you will understand how the short code is made, how the redirect works, and how the links are stored so they keep working.
What we are building
Our little service needs to do three jobs:
- Take a long URL and save it.
- Make a short code for it (like
aZ4k). - When someone opens
/aZ4k, send their browser to the original long URL.
That is the whole idea. Here is the big picture before we write any code.
There are two paths. The top path creates a short link. The bottom path uses one. Keep these two journeys in your head and the rest will feel easy.
Step 1: Create the project
First, make sure you have the .NET 10 SDK installed. Then open a terminal and create a new empty web project.
dotnet new web -n UrlShortener
cd UrlShortener
dotnet add package Microsoft.EntityFrameworkCore.SqliteThe web template gives you a minimal API. That means no controllers and very little ceremony. We also add the SQLite provider for EF Core. SQLite is a tiny database that lives in a single file on your disk. It is perfect for learning because there is nothing extra to install or run.
Step 2: Describe one short link
Every short link needs to remember a few things: a database Id, the long URL, the short code, when it was made, and how many times it was clicked. In EF Core we describe this with a plain C# class called an entity.
public class ShortLink
{
public int Id { get; set; }
public string LongUrl { get; set; } = string.Empty;
public string Code { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public int Clicks { get; set; }
}Each property becomes a column in a database table. The Id is special: the database fills it in automatically and makes sure every row gets a different number. We will use that number to build our short code in a moment.
Step 3: Set up the database context
EF Core needs a small class that knows about your tables. This is called a DbContext. Think of it as the door between your C# code and the database.
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options) { }
public DbSet<ShortLink> ShortLinks => Set<ShortLink>();
}The DbSet<ShortLink> line tells EF Core: "I want a table that holds ShortLink rows." That is all the setup the database needs for now.
Step 4: The heart of it - turning a number into a short code
This is the clever part, so we will go slowly. When we save a link, the database gives us an Id like 1, 2, 3, and so on. Those numbers get long fast. We want a short code instead.
The trick is called Base62 encoding. Normal numbers use 10 digits (0 to 9). Base62 uses 62 symbols: 0-9, then a-z, then A-Z. Because each position can hold one of 62 symbols, you can pack a big number into very few characters.
Here is how a few numbers look in Base62.
| Database Id | Base62 code | Why |
|---|---|---|
| 1 | 1 | small numbers stay short |
| 61 | Z | last single symbol |
| 62 | 10 | rolls over, like 9 to 10 in normal counting |
| 125000 | Wi4 | three characters hold a big number |
So https://yoursite.com/Wi4 is the short link for the 125,000th URL. Short and friendly. Here is the code that does the conversion.
public static class Base62
{
private const string Alphabet =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static string Encode(long number)
{
if (number == 0) return "0";
var chars = new Stack<char>();
while (number > 0)
{
chars.Push(Alphabet[(int)(number % 62)]);
number /= 62;
}
return new string(chars.ToArray());
}
}The loop keeps taking the remainder when dividing by 62, picks the matching symbol, then shrinks the number. We push onto a Stack so the characters come out in the right order. It is the same idea you used in school to convert numbers between bases.
How a number becomes a short code
Steps
Id
Start with the database Id, e.g. 125000
Divide by 62
Take the remainder and the quotient
Pick symbol
Remainder chooses one of 62 characters
Repeat
Keep going until the number is 0
Code
Read the symbols back to front: Wi4
Step 5: Wire up the app
Now we put it together in Program.cs. This file is the starting point of every minimal API app. We register the database, create the file if needed, and add our endpoints.
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlite("Data Source=links.db"));
var app = builder.Build();
// Create the database file on first run.
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Database.EnsureCreated();
}
app.Run();The AddDbContext line connects EF Core to a SQLite file called links.db. The small using block runs once at startup and makes sure the database file and table exist. We have no endpoints yet, so let us add them next.
Step 6: The "create short link" endpoint
This endpoint accepts a long URL, saves it, builds a code from the new Id, and returns the short link. We use a simple record for the incoming request.
record CreateRequest(string Url);
app.MapPost("/shorten", async (CreateRequest req, AppDbContext db) =>
{
if (!Uri.TryCreate(req.Url, UriKind.Absolute, out var parsed) ||
(parsed.Scheme != "http" && parsed.Scheme != "https"))
{
return Results.BadRequest("Please send a valid http or https URL.");
}
var link = new ShortLink
{
LongUrl = req.Url,
CreatedAt = DateTime.UtcNow
};
db.ShortLinks.Add(link);
await db.SaveChangesAsync(); // now link.Id is filled in
link.Code = Base62.Encode(link.Id);
await db.SaveChangesAsync(); // save the code too
return Results.Ok(new { code = link.Code, shortUrl = $"/{link.Code}" });
});Notice the two saves. The first save asks the database to insert the row and give us back the Id. Only then can we build the code from that Id. The second save stores the code. We also check the URL is real and uses http or https, because we should never trust input blindly.
Step 7: The redirect endpoint
This is the part visitors actually use. When someone opens /Wi4, we read the code, find the matching link, count the click, and send them on their way.
app.MapGet("/{code}", async (string code, AppDbContext db) =>
{
var link = await db.ShortLinks
.FirstOrDefaultAsync(l => l.Code == code);
if (link is null)
return Results.NotFound("That short link does not exist.");
link.Clicks++;
await db.SaveChangesAsync();
return Results.Redirect(link.LongUrl); // 302 by default
});Results.Redirect sends back an HTTP 302 response by default, which tells the browser "the thing you want is over here, go fetch it." The browser then loads the original site. We also add one to Clicks so we can see how popular each link is.
Following a short link
Steps
Open code
Visitor opens /Wi4 in the browser
Find link
Look up the code in the database
Count click
Add one to the click counter
Redirect
Send a 302 to the original long URL
Choosing the right redirect
People often ask which redirect to use. The two common ones behave differently, so here is a simple comparison.
| Status code | Meaning | When to use |
|---|---|---|
| 301 | Moved Permanently | The mapping will never change. Browsers cache it hard. |
| 302 | Found (temporary) | Normal choice. Lets you change targets and count clicks. |
For a URL shortener, 302 is usually the friendly default. Because browsers do not cache it forever, every click reaches your service, so your click counter stays correct and you can update where a link points later.
How the pieces fit together
Let us zoom out and see every part of the app in one place. This helps you keep the mental model clear as the project grows.
Every request lands in one of two flows. The "Creating" flow makes new links. The "Looking" flow uses them. The database sits in the middle, remembering everything between requests.
Testing it yourself
You do not need a fancy tool to try this. With the app running, open a second terminal and send a request.
# Create a short link
curl -X POST http://localhost:5000/shorten \
-H "Content-Type: application/json" \
-d "{\"url\":\"https://learn.microsoft.com\"}"
# You get back something like {"code":"1","shortUrl":"/1"}
# Now open http://localhost:5000/1 in a browser and watch it redirect.The first link gets the code 1. The second gets 2, and so on. Each redirect adds one to that link's click count. Congratulations, you have a working URL shortener.
Common mistakes to avoid
A few small traps catch beginners. Knowing them ahead of time saves hours.
- Forgetting the first save. The
Idis only0until the row is actually inserted. If you encode before saving, every code becomes0. Save first, then encode. - Trusting input. Always validate the incoming URL. A shortener that redirects anywhere can be abused to hide scam links.
- Using 301 too early. Because browsers cache 301 forever, a wrong target is hard to fix. Stick with 302 while you learn.
- Letting codes clash. Our Base62 code comes straight from the unique database
Id, so two links can never share a code. If you ever switch to random codes, you must check for collisions.
Where to go next
Once the basics work, you can grow this project in fun ways. You could add a friendly web page with a form. You could swap SQLite for SQL Server or PostgreSQL when you need more power. You could add caching with Redis so popular links redirect without touching the database every time. You could let users pick their own custom code, like /diwali-sale. Each of these is a small, satisfying upgrade on top of what you already built.
If you want to package and ship this as a real service, the same minimal API runs happily in a container and on cloud platforms, because .NET runs on Linux, Windows, and macOS.
References and further reading
- Tutorial: Create a Minimal API with ASP.NET Core (Microsoft Learn)
- Use a database with minimal API, EF Core, and ASP.NET Core (Microsoft Learn)
- Minimal APIs quick reference (Microsoft Learn)
- How to Build a URL Shortener in .NET (Code Maze)
- Minimal URL Shortener sample (dotnet-labs on GitHub)
Quick recap
- A URL shortener saves a long link and gives back a short code, then redirects visitors to the original link.
- Use the .NET 10 web template for a minimal API and EF Core with SQLite for easy storage.
- Save the link first to get its database
Id, then turn thatIdinto a short code with Base62 encoding. - The POST /shorten endpoint creates links; the
GET /{code}endpoint redirects with a 302. - Prefer 302 over 301 so you can count clicks and change targets later.
- Always validate the incoming URL, and grow the project later with a web form, Redis caching, or custom codes.
Related Posts
6 Steps for Setting Up a New .NET Project the Right Way
A friendly, step-by-step guide to starting a clean .NET 10 project with the right folder layout, central packages, analyzers, EditorConfig, and CI.
Build a Clean Architecture .NET App: A Hands-On PlaceOrder Tutorial
Build a Clean Architecture .NET 10 app from an empty solution to a working POST /orders minimal API. Four projects, one use case, EF Core, step by step.
How to Scale Long-Running API Requests in .NET: A Beginner's Guide
Learn how to handle slow, long-running API requests in .NET using the 202 Accepted pattern, background services, channels, and status polling.
Building Async APIs in ASP.NET Core the Right Way
Learn to build fast, safe async APIs in ASP.NET Core: async/await, CancellationToken, avoiding .Result deadlocks, and thread pool tips.
How to Implement Multitenancy in ASP.NET Core with EF Core
A simple, student-friendly guide to building multitenant apps in ASP.NET Core with EF Core using tenant resolution, global query filters, and per-tenant databases.
How to Build a Production-Ready Invoice Builder in .NET Using IronPDF
A simple, beginner-friendly guide to building a real invoice PDF generator in .NET with IronPDF, from HTML template to a clean download in your API.