Skip to main content
SEMastery

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.

11 min readUpdated April 6, 2026

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:

  1. Take a long URL and save it.
  2. Make a short code for it (like aZ4k).
  3. 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.

The two main journeys: creating a short link, and using one.

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.Sqlite

The 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.

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 IdBase62 codeWhy
11small numbers stay short
61Zlast single symbol
6210rolls over, like 9 to 10 in normal counting
125000Wi4three 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

Id
Divide by 62
Pick symbol
Repeat
Code

Steps

1

Id

Start with the database Id, e.g. 125000

2

Divide by 62

Take the remainder and the quotient

3

Pick symbol

Remainder chooses one of 62 characters

4

Repeat

Keep going until the number is 0

5

Code

Read the symbols back to front: Wi4

Base62 encoding, one step at a time.

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.

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.

What happens inside the shorten endpoint.

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

Open code
Find link
Count click
Redirect

Steps

1

Open code

Visitor opens /Wi4 in the browser

2

Find link

Look up the code in the database

3

Count click

Add one to the click counter

4

Redirect

Send a 302 to the original long URL

From short code to original website.

Choosing the right redirect

People often ask which redirect to use. The two common ones behave differently, so here is a simple comparison.

Status codeMeaningWhen to use
301Moved PermanentlyThe mapping will never change. Browsers cache it hard.
302Found (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.

The full shape of the URL shortener.

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 Id is only 0 until the row is actually inserted. If you encode before saving, every code becomes 0. 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

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 that Id into 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