Introduction
In my day-to-day development work, almost every application I build or maintain depends on APIs. Whether it is a web dashboard, a mobile app, or even a background service, RESTful APIs act as the backbone that connects everything together.
When I first started working with ASP.NET Core Web APIs, I struggled to understand where to start, how controllers talk to data, and how real projects are structured beyond tutorials. Most examples looked perfect on paper but didn’t explain why certain decisions were made.
In this article, I’ll walk you through building a simple but practical RESTful API using ASP.NET Core, based on a Book Management System. This is not just theory — it’s the same pattern I personally use as a foundation before integrating databases, authentication, or cloud deployment.
Step 1: Setting Up the ASP.NET Core Web API Project
I prefer starting with the CLI because it gives a clean, minimal setup and avoids unnecessary UI confusion.
dotnet new webapi -n BookStoreAPI
cd BookStoreAPI
This command creates:
At this stage, you can already run the project and see Swagger working, which helps beginners gain confidence early.
Step 2: Defining the Model (Book)
Before writing APIs, I always define what data I’m working with. For this example, we’ll manage books.
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public decimal Price { get; set; }
}
Why this matters:
Models represent real-world entities
They become the contract between client and server
Later, the same model can map directly to a database table
Step 3: Creating a Repository Layer (Why I Use It)
Many beginners directly write logic inside controllers. I used to do that too — until projects grew and became hard to maintain.
To keep things clean, I follow a repository pattern, even for small projects.
Interface
public interface IBookRepository
{
IEnumerable<Book> GetAll();
Book GetById(int id);
void Add(Book book);
void Update(Book book);
void Delete(int id);
}
Implementation (In-Memory Data)
public class BookRepository : IBookRepository
{
private readonly List<Book> _books = new();
public IEnumerable<Book> GetAll() => _books;
public Book GetById(int id) =>
_books.FirstOrDefault(b => b.Id == id);
public void Add(Book book) =>
_books.Add(book);
public void Update(Book book)
{
var existing = GetById(book.Id);
if (existing != null)
{
existing.Title = book.Title;
existing.Author = book.Author;
existing.Price = book.Price;
}
}
public void Delete(int id)
{
var book = GetById(id);
if (book != null)
_books.Remove(book);
}
}
Why in-memory first?
Step 4: Creating the Controller (Where REST Happens)
Controllers expose endpoints that clients consume.
[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{
private readonly IBookRepository _repository;
public BooksController(IBookRepository repository)
{
_repository = repository;
}
[HttpGet]
public IActionResult GetAll()
{
return Ok(_repository.GetAll());
}
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
var book = _repository.GetById(id);
if (book == null)
return NotFound("Book not found");
return Ok(book);
}
[HttpPost]
public IActionResult Add(Book book)
{
_repository.Add(book);
return CreatedAtAction(nameof(GetById),
new { id = book.Id }, book);
}
[HttpPut("{id}")]
public IActionResult Update(int id, Book book)
{
if (id != book.Id)
return BadRequest("ID mismatch");
_repository.Update(book);
return NoContent();
}
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
_repository.Delete(id);
return NoContent();
}
}
What I learned from experience:
Always return proper HTTP status codes
Avoid putting business logic inside controllers
Keep controllers thin and readable
Step 5: Registering Dependencies in Program.cs
Without dependency injection, the controller won’t know how to create IBookRepository.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSingleton<IBookRepository, BookRepository>();
var app = builder.Build();
app.MapControllers();
app.Run();
This single line makes the entire architecture flexible and testable.
Step 6: Testing Using Swagger (My Favorite Tool)
One thing I love about ASP.NET Core is built-in Swagger.
You can test:
GET /api/books
GET /api/books/{id}
POST /api/books
PUT /api/books/{id}
DELETE /api/books/{id}
Swagger helps validate APIs without writing a frontend, which saves huge development time.
(Here you should add Swagger screenshots – this is VERY important for approval)
Use Case
Imagine a mobile bookstore app:
The app fetches available books → GET
Admin adds new books → POST
Prices change → PUT
Old books removed → DELETE
This API becomes the backend engine powering all those actions.
Conclusion
Building RESTful APIs with ASP.NET Core is simple once the fundamentals are clear. Through this Book Management System, I demonstrated how I personally structure APIs using models, repositories, controllers, and dependency injection.
From here, you can extend this project by:
Adding Entity Framework Core
Implementing authentication (JWT)
Adding logging and validation
Deploying to Azure or IIS
This foundation is something I consistently use in real projects, and it scales well as applications grow.