Bookmark Manager API with .NET

Build a REST API with ASP.NET Core, MongoDB Atlas, and a GitHub Actions CI pipeline.

Introduction

30 Second Summary

Everyone saves links they want to revisit later. Browser bookmarks get buried fast, and there is no easy way to tag, search, or organize them programmatically.

In this project, you will build a REST API that manages bookmarks, connect it to a cloud database, and set up an automated pipeline that checks your code every time you push changes.

What You'll Build

A Bookmark Manager microservice built with ASP.NET Core Minimal APIs, backed by MongoDB Atlas for storage and GitHub Actions for continuous integration.

By the end of this project, you'll have:

  • A working REST API with full CRUD endpoints for creating, reading, updating, and deleting bookmarks.
  • A MongoDB Atlas cloud database storing your bookmark data as flexible documents.
  • A GitHub Actions CI pipeline that automatically builds and tests your code on every push.
  • Secret Mission: Add search and filter endpoints that query bookmarks by tag and date range.

Are there any prerequisites?

You should have a basic understanding of C# syntax (variables, classes, methods) and familiarity with what an API is conceptually. No prior experience with MongoDB or CI/CD is needed.

Set Up Your Development Environment

Before you can build your Bookmark Manager API, you need the right tools on your machine. Visual Studio 2022 is your development environment, and the .NET 8 SDK is the runtime that powers your API.

In this step, you will install Visual Studio 2022 Community, verify your tools are working, and create the starter project for your API.

In this step, get ready to:
  • Install Visual Studio 2022 Community with the ASP.NET workload.
  • Verify .NET 8 and Git are ready to use.
  • Create a new ASP.NET Core Web API project using Minimal APIs.
Install Visual Studio 2022 Community

Visual Studio 2022 Community is a free, full-featured IDE from Microsoft. It includes everything you need to build, run, and debug .NET applications.

  • Open your browser and go to the Visual Studio 2022 Community download page.
  • Click Free download to download the installer.
  • Run the downloaded installer file.
  • When the Visual Studio Installer opens, select the ASP.NET and web development workload checkbox.
  • Click Install in the bottom right corner.

What does the ASP.NET workload include?

The ASP.NET and web development workload bundles the .NET 8 SDK, web project templates, and debugging tools into a single install.

This means you do not need to install the .NET SDK separately.

The installation takes a few minutes depending on your internet speed. Once complete, Visual Studio 2022 is ready to launch.

Installation taking too long or failing?

Make sure you have at least 10 GB of free disk space. If the installer stalls, try restarting it. The installer resumes from where it left off.

Help me troubleshoot my Visual Studio 2022 installation

Verify your .NET and Git installation

Before creating a project, confirm that both .NET 8 and Git are available from the command line.

  • Press Windows key to open your search bar.
  • Type Developer Command Prompt for VS 2022 and press Enter to open it.
  • Check your .NET version by running this command:
dotnet --version

✔️ I see a version number starting with 8

Your .NET 8 SDK is installed and ready. The exact patch version may differ (for example, 8.0.405) but any 8.x.x version works for this project.

ⓧ Command not found or version is not 8.x

If dotnet is not recognized, the ASP.NET workload may not have installed correctly.

  • Open the Visual Studio Installer again.
  • Click Modify next to Visual Studio 2022.
  • Confirm the ASP.NET and web development workload is checked.
  • Click Modify to repair the installation.
  • Close and reopen the Developer Command Prompt, then try dotnet --version again.

Help me fix my .NET SDK installation

  • Verify Git is available by running this command:
git --version

You should see a version number like git version 2.44.0 or similar. Any recent version of Git works for this project.

Git not recognized?

If git is not recognized, download and install it from git-scm.com. After installing, close and reopen your terminal for the PATH changes to take effect.

Help me install Git on my machine

Create the Web API project

With your tools verified, it is time to create the starter project. You will use the ASP.NET Core Web API template, which scaffolds a project configured for building HTTP APIs.

  • Press Windows key to open your search bar.
  • Type Visual Studio 2022 and press Enter to open it.
  • On the start window, click Create a new project.
  • In the search box at the top, type ASP.NET Core Web API.
  • Select the ASP.NET Core Web API template (with the C# tag) and click Next.
  • In the Project name field, type BookmarkApi.
  • Choose a location on your Desktop or Documents folder for the project.
  • Click Next.

The Additional information dialog appears with framework and configuration options.

  • Set the Framework dropdown to .NET 8.0 (Long Term Support).
  • Uncheck the Use controllers checkbox. This creates a Minimal API project instead of a controller-based one.
  • Make sure Enable OpenAPI support is checked. This adds Swagger for testing your API in the browser.
  • Click Create.

Why Minimal APIs instead of controllers?

Microsoft recommends Minimal APIs for new microservice projects. They use less boilerplate, perform better, and let you focus on the actual API logic instead of framework scaffolding.

Visual Studio creates your project and opens it in the editor. The template includes a sample weather forecast endpoint that you do not need.

  • In the Solution Explorer on the right side, double-click Program.cs to open it.
  • Select all the code in the file (Ctrl+A).
  • Replace it with the following clean starter code:
var builder = WebApplication.CreateBuilder(args);

// Add Swagger for API testing in the browser
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Enable Swagger UI only in development
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.Run();

What does this code do?

  • `WebApplication.CreateBuilder(args)` sets up the web application with default configuration.
  • `AddEndpointsApiExplorer()` and `AddSwaggerGen()` register the Swagger services that generate a test UI for your API.
  • The `if (app.Environment.IsDevelopment())` block enables the Swagger UI only when running locally, keeping it hidden in production.
  • `app.UseHttpsRedirection()` automatically redirects HTTP requests to HTTPS for security.
  • `app.Run()` starts the web server and begins listening for requests.
  • Save the file by pressing Ctrl+S.
  • Press F5 to run the project.

A browser window opens to the Swagger UI page. The page will be empty since you have no endpoints yet, but seeing the Swagger interface confirms your project is running correctly.

  • Stop the app by pressing Shift+F5 in Visual Studio or closing the browser window.

Browser shows a certificate error?

If you see a security warning about the SSL certificate, Visual Studio may prompt you to trust the development certificate. Click Yes on both the Visual Studio dialog and the Windows security dialog to trust it.

Help me fix the SSL certificate error when running my .NET project

✔️ Awesome, I've got everything!

Great. Double check that you have saved Program.cs and that the project runs without errors.

ⓧ I'd like to double check the full code

Here is what Program.cs should look like at this point:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.Run();

And appsettings.json should look like this (generated by the template):

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Your development environment is ready and your starter project is running. Next up, you will connect your API to a real database by setting up MongoDB Atlas in the cloud.

Set Up MongoDB Atlas

Your development environment is ready and your BookmarkApi project is open in Visual Studio. Now your API needs somewhere to store bookmarks.

MongoDB Atlas is a cloud-hosted NoSQL database that stores data as flexible documents instead of rigid table rows. In this step, you'll create a free Atlas cluster and connect it to your project.

In this step, get ready to:
  • Create a MongoDB Atlas account and provision a free cluster.
  • Configure database access and network permissions.
  • Install the MongoDB driver in your .NET project.
Create a MongoDB Atlas account and cluster

MongoDB Atlas provides a free tier (M0) with 512 MB of storage. No credit card is required.

  • Open your browser and go to mongodb.com/cloud/atlas.
  • Click **Try Free** and sign up for a new account (or sign in if you already have one).
  • Click Try Free and sign up for a new account.
  • Once logged in, create a new project by clicking New Project in the top-left dropdown.
  • Name the project BookmarkApi and click Create Project.
  • Click Build a Database (or Create a Deployment).
  • Select the M0 FREE tier.
  • Choose a cloud provider and region closest to you.
  • Click Create Deployment.

Provisioning takes about 1-2 minutes. Wait until the cluster status shows a green checkmark before continuing.

What is a cluster?

A cluster is a group of servers that host your database. The M0 free tier gives you a shared cluster with 512 MB of storage. That is more than enough for this project.

Stuck on the Atlas sign-up page?

Some ad blockers interfere with the Atlas sign-up flow. Try disabling your ad blocker for this site, or use a private/incognito browser window.

Help me troubleshoot my MongoDB Atlas sign-up

Create a database user and configure network access

Atlas requires two things before any application can connect: a database user (credentials) and a network rule (which IP addresses are allowed in).

  • In the Atlas sidebar, click Database Access under the Security section.
  • Click Add New Database User.
  • Choose Password as the authentication method.
  • Enter a username and password. Write both down somewhere safe because you will need them shortly.
  • Under Built-in Role, select Read and write to any database.
  • Click Add User.

Now configure which IP addresses are allowed to reach your cluster.

  • In the Atlas sidebar, click Network Access under the Security section.
  • Click Add IP Address.
  • Click Allow Access from Anywhere to add 0.0.0.0/0. This is fine for development.
  • Click Confirm.

Why allow access from anywhere?

Setting 0.0.0.0/0 means any IP address can attempt to connect. Your database user credentials still protect the data. For a production application, you would restrict this to specific server IP addresses.

Don't see the Security section?

Atlas occasionally updates its sidebar layout. Look for a shield icon, or try the search bar at the top of the Atlas console and type Network Access.

Help me find Database Access and Network Access in Atlas

Get your connection string and add the MongoDB driver

The connection string is how your .NET app authenticates with Atlas. You will also install the official MongoDB driver package so your project can talk to the database.

  • In the Atlas sidebar, click Database to see your cluster.
  • Click Connect on your cluster.
  • Select Drivers as the connection method.
  • Choose C# / .NET as the driver.
  • Copy the connection string. It looks something like mongodb+srv://username:<password>@cluster0.xxxxx.mongodb.net/.
  • Replace <password> in the string with the actual password you created for your database user. Save this updated string somewhere you can access it later.

Keep your connection string handy

Paste the connection string into a temporary text file or note. You will add it to your project's configuration in the next step.

Now install the MongoDB .NET driver so your project can communicate with Atlas.

  • In Visual Studio, open the terminal by clicking View in the top menu bar, then selecting Terminal.
  • Make sure you are in the BookmarkApi project folder (the one containing Program.cs).
  • Install the MongoDB driver by running this command:
dotnet add package MongoDB.Driver --version 3.8.0

What does this command do?

This adds the official MongoDB .NET driver (version 3.8.0) to your project as a NuGet package. NuGet is .NET's package manager, similar to npm for JavaScript or pip for Python.

  • Verify the package was added by opening BookmarkApi.csproj in Solution Explorer. You should see a PackageReference line for MongoDB.Driver with version 3.8.0.

Seeing a 'project not found' error?

Make sure your terminal's working directory is the folder that contains BookmarkApi.csproj. You can navigate there by running cd BookmarkApi from the solution root.

Help me install the MongoDB.Driver NuGet package

Your cloud database is live and your project has the driver it needs to connect. Next up, you'll build the actual API endpoints that create, read, update, and delete bookmarks in MongoDB.

Build the Bookmark API

Your database is provisioned and ready to accept connections. Now it is time to build the API that talks to it.

You will create a data model that defines what a bookmark looks like, a service that handles all database communication, and a set of endpoints that expose CRUD operations to the outside world. By the end of this step, your project has a fully structured Minimal API ready to be tested.

In this step, get ready to:
  • Create the Bookmark model and MongoDB settings classes.
  • Build a service layer that performs CRUD operations against MongoDB Atlas.
  • Wire up RESTful API endpoints in Program.cs.
Create the Bookmark model

MongoDB stores data as documents (similar to JSON objects). Your API needs a C# class that maps to the shape of those documents so the MongoDB driver knows how to translate between C# objects and database records.

  • In the Solution Explorer panel (right side of Visual Studio), right-click the BookmarkApi project.
  • Select Add, then New Folder.
  • Name the folder Models.
  • Right-click the Models folder, select Add, then Class....
  • Name the file Bookmark.cs and click Add.
  • Replace the entire contents of the file with the following code:
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace BookmarkApi.Models;

public class Bookmark
{
    // Maps to MongoDB's _id field, stored as an ObjectId string
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string? Id { get; set; }

    public string Title { get; set; } = string.Empty;

    public string Url { get; set; } = string.Empty;

    // Optional field - not every bookmark needs a description
    public string? Description { get; set; }

    // A list of tags for categorizing bookmarks
    public List<string> Tags { get; set; } = new();

    // Automatically set to the current UTC time when created
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

What does this code do?

This class defines the shape of a bookmark document in MongoDB.

  • [BsonId] tells MongoDB this property is the document's unique identifier.
  • [BsonRepresentation(BsonType.ObjectId)] lets you work with the ID as a regular string in C# while MongoDB stores it as its native ObjectId type.
  • Default values like string.Empty and new() prevent null reference errors when creating new bookmarks.
  • Save Bookmark.cs by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Confirm the file appears under the Models folder in Solution Explorer.

Seeing red squiggly lines?

If you see errors under MongoDB.Bson or BsonType, the MongoDB.Driver NuGet package may not be fully restored. Right-click the solution in Solution Explorer and select Restore NuGet Packages.

Still stuck? Why am I getting errors in Bookmark.cs after adding MongoDB attributes?

Create the settings class and configure appsettings.json

Your API needs to know where MongoDB lives. A settings class paired with appsettings.json keeps connection details in one place, separate from your application logic.

  • Right-click the Models folder, select Add, then Class....
  • Name the file MongoDbSettings.cs and click Add.
  • Replace the entire contents with the following code:
namespace BookmarkApi.Models;

public class MongoDbSettings
{
    // The Atlas connection string (e.g., mongodb+srv://...)
    public string ConnectionString { get; set; } = string.Empty;

    // The database name within your Atlas cluster
    public string DatabaseName { get; set; } = string.Empty;

    // The collection name where bookmarks are stored
    public string CollectionName { get; set; } = string.Empty;
}

What does this code do?

This class acts as a strongly-typed container for your MongoDB connection details. ASP.NET Core's options pattern reads values from appsettings.json and maps them into this class automatically. No manual string parsing needed.

  • Save MongoDbSettings.cs.
  • Now open appsettings.json from the project root in Solution Explorer.
  • Replace the entire contents of the file with the following, pasting your Atlas connection string where indicated:
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "MongoDb": {
    "ConnectionString": "your-connection-string-here",
    "DatabaseName": "bookmarkdb",
    "CollectionName": "bookmarks"
  }
}

What does this code do?

  • The "MongoDb" section name must match exactly what you reference in Program.cs later.
  • "ConnectionString" is the Atlas connection string you copied in the previous step. Replace the placeholder with your real string, including your database user password.
  • "bookmarkdb" and "bookmarks" are names for the database and collection that MongoDB creates automatically the first time you insert a document.
  • Replace your-connection-string-here with the connection string you copied from Atlas in Step 2.
  • Make sure you replace the <password> placeholder inside the connection string with your actual database user password.
  • Save appsettings.json.

Not sure where your connection string is?

Go back to the Atlas dashboard, click Connect on your cluster, select Drivers, and copy the string again. It starts with mongodb+srv://.

Need help? How do I find my MongoDB Atlas connection string?

Create the BookmarkService

The service layer handles all direct communication with MongoDB. Endpoints call the service, and the service talks to the database. This separation keeps your API routes clean and your database logic reusable.

  • In Solution Explorer, right-click the BookmarkApi project.
  • Select Add, then New Folder.
  • Name the folder Services.
  • Right-click the Services folder, select Add, then Class....
  • Name the file BookmarkService.cs and click Add.
  • Replace the entire contents with the following code:
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using BookmarkApi.Models;

namespace BookmarkApi.Services;

public class BookmarkService
{
    // Holds a reference to the bookmarks collection in MongoDB
    private readonly IMongoCollection<Bookmark> _bookmarks;

    public BookmarkService(IOptions<MongoDbSettings> settings)
    {
        // Create a connection to MongoDB using the Atlas connection string
        var client = new MongoClient(settings.Value.ConnectionString);
        var database = client.GetDatabase(settings.Value.DatabaseName);
        _bookmarks = database.GetCollection<Bookmark>(settings.Value.CollectionName);
    }

    // Retrieve all bookmarks from the collection
    public async Task<List<Bookmark>> GetAllAsync() =>
        await _bookmarks.Find(_ => true).ToListAsync();

    // Find a single bookmark by its MongoDB ObjectId
    public async Task<Bookmark?> GetByIdAsync(string id) =>
        await _bookmarks.Find(b => b.Id == id).FirstOrDefaultAsync();

    // Insert a new bookmark document
    public async Task CreateAsync(Bookmark bookmark) =>
        await _bookmarks.InsertOneAsync(bookmark);

    // Replace an existing bookmark document entirely
    public async Task UpdateAsync(string id, Bookmark bookmark) =>
        await _bookmarks.ReplaceOneAsync(b => b.Id == id, bookmark);

    // Remove a bookmark by its ID
    public async Task DeleteAsync(string id) =>
        await _bookmarks.DeleteOneAsync(b => b.Id == id);
}

What does this code do?

  • The constructor receives settings via dependency injection. ASP.NET Core automatically passes in the values from your appsettings.json file.
  • MongoClient manages a pool of connections to Atlas. You only need one instance per application.
  • Each async method (GetAllAsync, CreateAsync, etc.) maps to one CRUD operation.
  • Find(_ => true) means "match every document" since we want all bookmarks returned.
  • Save BookmarkService.cs.
  • Confirm the file appears under the Services folder in Solution Explorer.

Seeing errors in BookmarkService.cs?

Check that the namespace is BookmarkApi.Services (matching the folder name). If IOptions or MongoClient show errors, make sure the three using statements at the top are present.

Still stuck? Help me fix errors in my BookmarkService.cs file.

Wire up the API endpoints

The model defines the data shape. The service handles database operations. Now you need endpoints that accept HTTP requests and delegate work to the service. This is where Minimal APIs shine. You define routes directly in Program.cs without the overhead of controller classes.

  • Open Program.cs from the project root in Solution Explorer.
  • Replace the entire contents of the file with the following code. This registers your services and defines the GET endpoints:
using BookmarkApi.Models;
using BookmarkApi.Services;

var builder = WebApplication.CreateBuilder(args);

// Register MongoDB settings from appsettings.json
builder.Services.Configure<MongoDbSettings>(
    builder.Configuration.GetSection("MongoDb"));
// Register BookmarkService as a singleton (one instance for the app's lifetime)
builder.Services.AddSingleton<BookmarkService>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

// Group all bookmark endpoints under /api/bookmarks
var bookmarks = app.MapGroup("/api/bookmarks");

// GET all bookmarks
bookmarks.MapGet("/", async (BookmarkService service) =>
    await service.GetAllAsync());

// GET a single bookmark by its ID
bookmarks.MapGet("/{id}", async (string id, BookmarkService service) =>
    await service.GetByIdAsync(id) is Bookmark bookmark
        ? Results.Ok(bookmark)
        : Results.NotFound());

app.Run();

What does this code do?

  • Configure<MongoDbSettings> reads the "MongoDb" section from appsettings.json and makes it available via dependency injection.
  • AddSingleton<BookmarkService>() tells ASP.NET Core to create one BookmarkService instance and reuse it for every request.
  • MapGroup("/api/bookmarks") creates a shared route prefix so you do not repeat it on every endpoint.
  • The GET by ID endpoint uses pattern matching (is Bookmark bookmark) to return 200 OK if found or 404 Not Found if null.
  • Save Program.cs.
  • Confirm there are no red squiggly lines in the editor. The project should compile without errors.

Now add the POST, PUT, and DELETE endpoints that complete your CRUD operations. These go between the last GET endpoint and the app.Run(); line.

  • In Program.cs, find this line:
app.Run();
  • Add the following code directly above app.Run();:
// POST a new bookmark
bookmarks.MapPost("/", async (Bookmark bookmark, BookmarkService service) =>
{
    await service.CreateAsync(bookmark);
    return Results.Created($"/api/bookmarks/{bookmark.Id}", bookmark);
});

// PUT (update) an existing bookmark by ID
bookmarks.MapPut("/{id}", async (string id, Bookmark bookmark, BookmarkService service) =>
{
    var existing = await service.GetByIdAsync(id);
    if (existing is null) return Results.NotFound();
    bookmark.Id = id;
    await service.UpdateAsync(id, bookmark);
    return Results.NoContent();
});

// DELETE a bookmark by ID
bookmarks.MapDelete("/{id}", async (string id, BookmarkService service) =>
{
    var existing = await service.GetByIdAsync(id);
    if (existing is null) return Results.NotFound();
    await service.DeleteAsync(id);
    return Results.NoContent();
});

What does this code do?

  • The POST endpoint creates a new bookmark and returns a 201 Created status with the bookmark's new ID in the response URL.
  • The PUT endpoint first checks if the bookmark exists. If not, it returns 404. Otherwise, it replaces the entire document and returns 204 No Content.
  • The DELETE endpoint follows the same pattern: check existence first, then delete. This prevents silent failures on nonexistent IDs.
  • bookmark.Id = id in the PUT handler ensures the document keeps its original ID even if the request body contains a different one.
  • Save Program.cs.
  • Confirm no errors appear in the editor. Your project now has all five CRUD endpoints defined.

Seeing errors about missing types or namespaces?

Make sure the two using statements at the top of Program.cs are using BookmarkApi.Models; and using BookmarkApi.Services;. Without these, the compiler cannot find Bookmark, MongoDbSettings, or BookmarkService.

Need help? Help me fix namespace or type errors in Program.cs for my Bookmark API.

✔️ Awesome, I've got everything!

Great. Double check you have saved all files: Bookmark.cs, MongoDbSettings.cs, appsettings.json, BookmarkService.cs, and Program.cs.

ⓧ I'd like to double check the full code

Here is the complete Program.cs file for comparison:

using BookmarkApi.Models;
using BookmarkApi.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<MongoDbSettings>(
    builder.Configuration.GetSection("MongoDb"));
builder.Services.AddSingleton<BookmarkService>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

var bookmarks = app.MapGroup("/api/bookmarks");

bookmarks.MapGet("/", async (BookmarkService service) =>
    await service.GetAllAsync());

bookmarks.MapGet("/{id}", async (string id, BookmarkService service) =>
    await service.GetByIdAsync(id) is Bookmark bookmark
        ? Results.Ok(bookmark)
        : Results.NotFound());

bookmarks.MapPost("/", async (Bookmark bookmark, BookmarkService service) =>
{
    await service.CreateAsync(bookmark);
    return Results.Created($"/api/bookmarks/{bookmark.Id}", bookmark);
});

bookmarks.MapPut("/{id}", async (string id, Bookmark bookmark, BookmarkService service) =>
{
    var existing = await service.GetByIdAsync(id);
    if (existing is null) return Results.NotFound();
    bookmark.Id = id;
    await service.UpdateAsync(id, bookmark);
    return Results.NoContent();
});

bookmarks.MapDelete("/{id}", async (string id, BookmarkService service) =>
{
    var existing = await service.GetByIdAsync(id);
    if (existing is null) return Results.NotFound();
    await service.DeleteAsync(id);
    return Results.NoContent();
});

app.Run();

And here is the complete Services/BookmarkService.cs:

using Microsoft.Extensions.Options;
using MongoDB.Driver;
using BookmarkApi.Models;

namespace BookmarkApi.Services;

public class BookmarkService
{
    private readonly IMongoCollection<Bookmark> _bookmarks;

    public BookmarkService(IOptions<MongoDbSettings> settings)
    {
        var client = new MongoClient(settings.Value.ConnectionString);
        var database = client.GetDatabase(settings.Value.DatabaseName);
        _bookmarks = database.GetCollection<Bookmark>(settings.Value.CollectionName);
    }

    public async Task<List<Bookmark>> GetAllAsync() =>
        await _bookmarks.Find(_ => true).ToListAsync();

    public async Task<Bookmark?> GetByIdAsync(string id) =>
        await _bookmarks.Find(b => b.Id == id).FirstOrDefaultAsync();

    public async Task CreateAsync(Bookmark bookmark) =>
        await _bookmarks.InsertOneAsync(bookmark);

    public async Task UpdateAsync(string id, Bookmark bookmark) =>
        await _bookmarks.ReplaceOneAsync(b => b.Id == id, bookmark);

    public async Task DeleteAsync(string id) =>
        await _bookmarks.DeleteOneAsync(b => b.Id == id);
}

And here is Models/Bookmark.cs:

using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace BookmarkApi.Models;

public class Bookmark
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string? Id { get; set; }

    public string Title { get; set; } = string.Empty;

    public string Url { get; set; } = string.Empty;

    public string? Description { get; set; }

    public List<string> Tags { get; set; } = new();

    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

And Models/MongoDbSettings.cs:

namespace BookmarkApi.Models;

public class MongoDbSettings
{
    public string ConnectionString { get; set; } = string.Empty;
    public string DatabaseName { get; set; } = string.Empty;
    public string CollectionName { get; set; } = string.Empty;
}

And appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "MongoDb": {
    "ConnectionString": "your-connection-string-here",
    "DatabaseName": "bookmarkdb",
    "CollectionName": "bookmarks"
  }
}

Why Minimal APIs over controllers?

Traditional ASP.NET Core APIs use controller classes with route attributes on each method. Minimal APIs let you define routes directly in Program.cs with less boilerplate. Microsoft recommends them for microservices because they are lighter, faster to scaffold, and easier to read for small focused services like this bookmark API.

Your API is fully wired up. In the next step, you will run it locally, test every endpoint through Swagger UI, and write unit tests to prove everything works.

Test Your API Locally

Your API code is complete. You have endpoints, a service layer, and a live MongoDB Atlas database ready to store bookmarks.

Now it is time to prove it all works together. You will run the API, test every endpoint through Swagger UI, and then add unit tests that verify your code independently of the database.

In this step, get ready to:
  • Run the API and test all five CRUD endpoints through Swagger UI.
  • Add a unit test project that validates your Bookmark model.
  • Run the tests and confirm they pass.
Run the app and test with Swagger

Swagger UI is a built-in tool that lets you send requests to your API directly from the browser. It auto-generates a visual interface from your endpoint definitions.

  • In Visual Studio, press F5 to run the project.

What should I see?

Your default browser opens to the Swagger UI page. You will see a list of five endpoints under /api/bookmarks: GET, GET by id, POST, PUT, and DELETE.

Now test each endpoint to confirm your API communicates with MongoDB Atlas correctly.

  • Click on the POST /api/bookmarks endpoint to expand it.
  • Click Try it out.
  • Replace the request body with this JSON:
{
  "title": "ASP.NET Core Docs",
  "url": "https://learn.microsoft.com/aspnet/core",
  "description": "Official Microsoft documentation",
  "tags": ["dotnet", "documentation"]
}
  • Click Execute.

You should see a 201 Created response with your bookmark data, including a generated id field.

  • Copy the id value from the response body.
  • Click on the GET /api/bookmarks endpoint, click Try it out, then click Execute.

You should see a 200 OK response containing an array with your bookmark.

  • Test the GET /api/bookmarks/{id} endpoint by pasting your copied id into the id field and clicking Execute.
  • Test the PUT /api/bookmarks/{id} endpoint by pasting the same id and changing the title to Updated Bookmark in the request body.
  • Test the DELETE /api/bookmarks/{id} endpoint by pasting the same id and clicking Execute.

PUT should return 204 No Content, and DELETE should also return 204 No Content. These confirm your updates and deletions are reaching MongoDB.

Seeing a 500 Internal Server Error?

This usually means your connection string in appsettings.json is incorrect. Double check that you replaced the <password> placeholder with your actual database user password.

Also confirm your IP address is still allowed in MongoDB Atlas under Network Access. If your IP has changed since the last step, add the new one.

Help me debug my MongoDB connection error in ASP.NET Core

Add a unit test project

Unit tests verify individual pieces of your code in isolation. They run without needing a database connection, which makes them perfect for automated pipelines.

  • Stop the running app by pressing Shift+F5 in Visual Studio.
  • In Solution Explorer, right-click the solution name at the top.
  • Select Add, then New Project.
  • Search for xUnit Test Project in the template search box.
  • Select xUnit Test Project and click Next.
  • Name the project BookmarkApi.Tests and click Next.
  • Keep .NET 8.0 as the framework and click Create.

Now add a reference so the test project can access your API code.

  • In Solution Explorer, right-click the BookmarkApi.Tests project.
  • Select Add, then Project Reference.
  • Check the box next to BookmarkApi and click OK.

Visual Studio scaffolded a default test file called UnitTest1.cs. You will replace its content with meaningful tests for your Bookmark model.

  • In Solution Explorer, expand the BookmarkApi.Tests project.
  • Right-click UnitTest1.cs and select Rename.
  • Rename it to BookmarkTests.cs.
  • Double-click BookmarkTests.cs to open it, then replace all the content with the following code:
using BookmarkApi.Models;

namespace BookmarkApi.Tests;

public class BookmarkTests
{
    [Fact]
    public void Bookmark_DefaultValues_AreCorrect()
    {
        // Create a bookmark with no arguments to check defaults
        var bookmark = new Bookmark();

        Assert.Equal(string.Empty, bookmark.Title);
        Assert.Equal(string.Empty, bookmark.Url);
        Assert.Null(bookmark.Description);
        Assert.Empty(bookmark.Tags);
    }

    [Fact]
    public void Bookmark_SetProperties_ReturnsCorrectValues()
    {
        // Create a bookmark with specific values and verify each one
        var bookmark = new Bookmark
        {
            Title = "ASP.NET Core Docs",
            Url = "https://learn.microsoft.com/aspnet/core",
            Description = "Official Microsoft documentation",
            Tags = new List<string> { "dotnet", "documentation", "microsoft" }
        };

        Assert.Equal("ASP.NET Core Docs", bookmark.Title);
        Assert.Equal("https://learn.microsoft.com/aspnet/core", bookmark.Url);
        Assert.Equal("Official Microsoft documentation", bookmark.Description);
        Assert.Equal(3, bookmark.Tags.Count);
        Assert.Contains("dotnet", bookmark.Tags);
    }
}

What does this code do?

The first test creates a Bookmark with no values and checks that the defaults are correct. Title and Url should be empty strings, Description should be null, and Tags should be an empty list.

The second test creates a Bookmark with specific property values and verifies each one is stored and returned correctly. These tests validate your model works without needing a live database connection.

  • Save BookmarkTests.cs.

Seeing red squiggles under BookmarkApi.Models?

Make sure you added the project reference to BookmarkApi. Right-click the BookmarkApi.Tests project, select Add > Project Reference, and check the box next to BookmarkApi.

Help me fix a missing project reference in my xUnit test project

Run the tests

Running tests from the terminal is exactly what your CI pipeline will do later. Proving they pass here means the pipeline will pass too.

  • Open the terminal in Visual Studio by clicking View in the top menu bar, then Terminal.
  • Run the tests by running this command:
dotnet test

You should see output showing 2 tests passed with 0 failures.

What should I see?

The terminal output includes a summary line like Passed! - Failed: 0, Passed: 2, Skipped: 0, Total: 2. Both test names appear in the output with a checkmark or "Passed" label.

Tests failing or not found?

Make sure your terminal is at the solution root folder (the folder containing both BookmarkApi and BookmarkApi.Tests folders). If you are inside one of the project folders, navigate up one level with cd .. and try again.

Help me troubleshoot why dotnet test cannot find or run my xUnit tests

✔️ Awesome, I've got everything!

Great. Double check you have saved all your files before moving on.

ⓧ I'd like to double check the full code

Here is the complete test file. Compare it against yours to spot any differences.

using BookmarkApi.Models;

namespace BookmarkApi.Tests;

public class BookmarkTests
{
    [Fact]
    public void Bookmark_DefaultValues_AreCorrect()
    {
        var bookmark = new Bookmark();

        Assert.Equal(string.Empty, bookmark.Title);
        Assert.Equal(string.Empty, bookmark.Url);
        Assert.Null(bookmark.Description);
        Assert.Empty(bookmark.Tags);
    }

    [Fact]
    public void Bookmark_SetProperties_ReturnsCorrectValues()
    {
        var bookmark = new Bookmark
        {
            Title = "ASP.NET Core Docs",
            Url = "https://learn.microsoft.com/aspnet/core",
            Description = "Official Microsoft documentation",
            Tags = new List<string> { "dotnet", "documentation", "microsoft" }
        };

        Assert.Equal("ASP.NET Core Docs", bookmark.Title);
        Assert.Equal("https://learn.microsoft.com/aspnet/core", bookmark.Url);
        Assert.Equal("Official Microsoft documentation", bookmark.Description);
        Assert.Equal(3, bookmark.Tags.Count);
        Assert.Contains("dotnet", bookmark.Tags);
    }
}

Your API works locally and your tests pass. Next up, you will push everything to GitHub and set up a CI pipeline that runs these tests automatically on every push.

Set Up CI/CD with GitHub Actions

Your tests pass locally, which means your Bookmark model behaves exactly as expected. But what happens when you make changes next week and forget to run the tests before deploying?

This is the problem CI/CD solves. In this step, you will push your project to GitHub and create a GitHub Actions pipeline that automatically builds and tests your code every time you push changes.

In this step, get ready to:
  • Initialize a Git repository and push your project to GitHub.
  • Create a GitHub Actions workflow that builds and tests your code.
  • Verify your CI pipeline passes with a green checkmark.
Initialize a Git repository and push to GitHub

Git tracks every change you make to your code, so you can always roll back to a working version. Your first step is to turn your solution folder into a Git repository.

  • In Visual Studio, open the terminal by clicking View in the top menu bar, then Terminal.
  • Make sure the terminal is at your solution root folder (the folder containing BookmarkApi.sln).
  • Initialize a Git repository and generate a .NET-specific .gitignore file by running these commands:
git init
dotnet new gitignore

What does this do?

git init creates a hidden .git folder that turns this directory into a Git repository.

dotnet new gitignore generates a standard .NET .gitignore file that tells Git to skip build outputs, NuGet packages, and Visual Studio temporary files. This keeps your repository clean.

  • Confirm the .gitignore file was created by running:
ls .gitignore
  • You should see .gitignore listed in the output.
  • Stage all project files and create your first commit by running these commands:
git add .
git commit -m "Initial commit: Bookmark API with MongoDB"

What does this do?

git add . stages every file in the folder (except those listed in .gitignore) for the next commit.

git commit -m saves a snapshot of all staged files with a descriptive message. This is your project's first checkpoint.

  • You should see output listing the files that were committed. The message will end with something like create mode 100644 for each file.

Seeing a 'please tell me who you are' error?

Git needs your identity before it can commit. Run these two commands with your own name and email:

git config --global user.email "you@example.com"
git config --global user.name "Your Name"

Then run the git commit command again. Why won't Git let me commit?

Now you need a remote repository on GitHub to store your code online and trigger the CI pipeline.

  • Open your browser and go to github.com.
  • Click the + icon in the top right corner, then select New repository.
  • Enter BookmarkApi as the repository name.
  • Select Public for visibility.
  • Leave all other options unchecked (no README, no .gitignore, no license).
  • Click Create repository.

GitHub shows you a quick setup page with commands. You will use those to connect your local repo to GitHub.

  • Back in the Visual Studio terminal, add the GitHub remote and push your code by running these commands:
git remote add origin [[YOUR_REPO_URL="https://github.com/YOUR_USERNAME/BookmarkApi.git"]]
git branch -M main
git push -u origin main

What does this do?

git remote add origin connects your local repository to the GitHub repository you just created.

git branch -M main renames your default branch to main. Then git push -u origin main uploads all your committed files to GitHub.

  • Refresh your GitHub repository page in the browser. You should see all your project files listed (Models, Services, Program.cs, etc.).

Push failed or authentication error?

If Git asks for a username and password, GitHub no longer accepts passwords for HTTPS pushes. You need a Personal Access Token instead. Go to GitHub Settings > Developer settings > Personal access tokens > Tokens (classic) and generate a token with repo scope. Use that token as your password when prompted.

Help me fix my GitHub push authentication

Create the GitHub Actions workflow

GitHub Actions reads workflow files from a special folder in your repository. Each YAML file in that folder defines an automated pipeline that runs when certain events happen (like pushing code).

  • Create the workflow directory by running this command:
mkdir -p .github/workflows
  • Create a new file called ci.yml inside the .github/workflows folder. In Visual Studio, right-click the .github/workflows folder in Solution Explorer, select Add > New Item, and name it ci.yml.
  • Paste the following workflow definition into ci.yml:
# Defines the CI pipeline for the Bookmark API
name: .NET CI

# Trigger on pushes and pull requests to main
on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    # Check out the repository code
    - uses: actions/checkout@v6
    # Install .NET 8 SDK
    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: 8.0.x
    # Download NuGet packages
    - name: Restore dependencies
      run: dotnet restore
    # Compile the solution
    - name: Build
      run: dotnet build --no-restore
    # Run unit tests
    - name: Test
      run: dotnet test --no-build --verbosity normal

What does this workflow do?

The on: section triggers this pipeline whenever you push to main or open a pull request targeting main.

The steps: section runs five actions in order: check out code, install .NET 8.0.x, restore packages, build, and run tests. If any step fails, the pipeline stops and marks the run as failed.

Why don't the tests need a live MongoDB connection?

Your unit tests only validate the Bookmark model's properties and defaults. They never call the BookmarkService or connect to a database. This is intentional. Testing against a cloud database in CI requires secrets management and adds complexity. The model-level tests prove the pipeline works without needing database access.

  • Save ci.yml.

✔️ Awesome, I've got everything!

Great. Double check you have saved the file before moving on.

ⓧ I'd like to double check the full code

Here is the complete ci.yml file:

name: .NET CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v6
    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: 8.0.x
    - name: Restore dependencies
      run: dotnet restore
    - name: Build
      run: dotnet build --no-restore
    - name: Test
      run: dotnet test --no-build --verbosity normal
Push the workflow and verify the pipeline

The workflow file only runs once it exists on GitHub. You need to commit and push it just like any other code change.

  • Stage, commit, and push the workflow file by running these commands:
git add .
git commit -m "Add CI workflow"
git push
  • In your browser, go to your GitHub repository.
  • Click the Actions tab at the top of the page.
  • You should see a workflow run in progress or already completed. Click on the run to see its details.

Each step in the pipeline shows a green checkmark when it passes. All five steps (checkout, setup .NET, restore, build, test) should be green.

Seeing a red X instead of green?

Click on the failed step to see the error output. Common causes:

  • Indentation errors in ci.yml. YAML is sensitive to spaces. Make sure you used spaces (not tabs) and the indentation matches the code above exactly.
  • Missing test project reference. If the build step fails with a reference error, make sure BookmarkApi.Tests has a project reference to BookmarkApi.

Help me debug my failing GitHub Actions pipeline

The final touch is adding a CI badge to your repository's README. This badge automatically shows whether your latest build is passing or failing.

  • In the Actions tab, click on your workflow name (.NET CI) in the left sidebar.
  • Click the ... (three dots) button near the top right of the workflow list, then select Create status badge.
  • Copy the Markdown snippet that appears.
  • Back in Visual Studio, create a file called README.md at your solution root (the same folder as BookmarkApi.sln).
  • Paste the badge Markdown as the first line of README.md.
  • Save README.md.
  • Commit and push the badge by running these commands:
git add .
git commit -m "Add CI badge to README"
git push
  • Refresh your repository's main page on GitHub. You should see a green "passing" badge at the top of your README.

Badge takes a moment to update

The badge image is cached by GitHub. If it still shows "no status" after pushing, wait 30 seconds and refresh the page.

Secret mission

Add Search and Filter Endpoints

Your API handles basic CRUD, but real-world APIs need query capabilities. Extend your Bookmark Manager with search and filter endpoints that showcase MongoDB's strengths with document data.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project uses MongoDB Atlas free tier and GitHub Actions free minutes for public repos, so there are no ongoing costs.

Resources you used:

  • BookmarkApi .NET project (local).
  • BookmarkApi.Tests unit test project (local).
  • MongoDB Atlas free tier cluster (M0).
  • MongoDB Atlas database user and network access rules.
  • GitHub repository with GitHub Actions CI workflow.

Keep everything running

No action needed. Choose this if you are still actively building or plan to extend the project with the Secret Mission.

  • Your MongoDB Atlas M0 cluster stays active at no cost (512 MB storage limit).
  • Your GitHub repository and Actions workflow remain available for free on public repos.
  • Your local project files stay on your machine as-is.

Pause - I'll come back to this later

Free up local resources while keeping your cloud services intact for when you return.

  • Close Visual Studio 2022 to free up memory.
  • Your MongoDB Atlas cluster pauses automatically after 60 days of inactivity. No action needed.
  • Your GitHub repository and workflow will remain available whenever you push next.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Delete your local project folder from your Desktop.
  • In MongoDB Atlas, go to your cluster and click the three dots menu, then select **Terminate** to delete the cluster and all stored data.
  • On GitHub, go to your repository's **Settings** tab, scroll to the bottom, and click **Delete this repository**.
  • Optionally uninstall Visual Studio 2022 Community from **Settings**, then **Apps**, then **Apps & features** if you no longer need it.

Nice Work!

Nice Work!

Nice work! You just built a production-structured .NET microservice from scratch and shipped it with a fully automated CI pipeline.

You've learned how to:

  • Build a RESTful API with full CRUD operations using ASP.NET Core Minimal APIs backed by a MongoDB Atlas database.
  • Structure a .NET project with dependency injection, separated Models and Services folders, and unit tests that validate your code independently.
  • Set up a GitHub Actions CI pipeline that automatically builds and tests your code on every push to a public repository.
  • Secret Mission: Add search and filter endpoints that leverage MongoDB's document query capabilities for tag matching and date range filtering.

Ready to quiz yourself?