
Ravi Kumar
••5 min read
Build a Complete Product CRUD API with ASP.NET Core (.NET 10)
Creating a clean, well-documented CRUD API is a foundational skill for modern backend development. In this guide, we’ll walk through a production-ready Product CRUD API built with ASP.NET Core Web API (.NET 10, C# 14)—covering design principles, full controller logic, testing, Swagger integration, and clear next steps for scaling to production.

Overview
This API demonstrates RESTful best practices using a simple in-memory store to keep the focus on clarity and correctness. It’s ideal for learning, demos, interviews, and as a starting point for real projects.
What you’ll get
- Full CRUD operations (Create, Read, Update, Delete)
- Clean REST endpoints and named routes
- Correct HTTP status codes
- Strongly-typed responses
- Swagger/OpenAPI for instant testing
- Clear testing workflows (.http, Swagger, Postman)
Typical use cases
- E-commerce product catalogs
- Inventory management systems
- Mobile app backends
- Third-party integrations
Product Model
Location: CRUD Operation/Product.cs
namespace CRUD_Operation
{
public class Product
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public decimal Price { get; set; }
public int Stock { get; set; }
public string? Category { get; set; }
}
}
Why this design?
- decimal for Price → avoids floating-point precision issues
- Nullable strings → flexible input for optional fields
- Flat structure → simple, readable, and easy to extend
ProductController (Complete CRUD)
Location: CRUD Operation/Controllers/ProductController.cs
using Microsoft.AspNetCore.Mvc;
namespace CRUD_Operation.Controllers
{
[ApiController]
[Route("[controller]")]
public class ProductController : ControllerBase
{
private static List<Product> _products = new();
private static int _nextId = 1;
[HttpGet(Name = "GetProducts")]
public ActionResult<IEnumerable<Product>> Get()
{
return Ok(_products);
}
[HttpGet("{id}", Name = "GetProductById")]
public ActionResult<Product> GetById(int id)
{
var product = _products.FirstOrDefault(p => p.Id == id);
return product == null ? NotFound() : Ok(product);
}
[HttpPost(Name = "CreateProduct")]
public ActionResult<Product> Create([FromBody] Product product)
{
product.Id = _nextId++;
_products.Add(product);
return CreatedAtRoute("GetProductById", new { id = product.Id }, product);
}
[HttpPut("{id}", Name = "UpdateProduct")]
public ActionResult<Product> Update(int id, [FromBody] Product product)
{
var existing = _products.FirstOrDefault(p => p.Id == id);
if (existing == null) return NotFound();
existing.Name = product.Name;
existing.Description = product.Description;
existing.Price = product.Price;
existing.Stock = product.Stock;
existing.Category = product.Category;
return Ok(existing);
}
[HttpDelete("{id}", Name = "DeleteProduct")]
public ActionResult Delete(int id)
{
var product = _products.FirstOrDefault(p => p.Id == id);
if (product == null) return NotFound();
_products.Remove(product);
return NoContent();
}
}
} Key highlights
- [ApiController] gives automatic validation and better defaults
- Named routes enable clean Location headers on POST
- Proper status codes (200, 201, 204, 404)
- Simple in-memory storage for fast iteration
CRUD Operations Explained (Quick)
Create (POST /product)
- Assigns auto-incremented ID
- Returns 201 Created with Location header
Read All (GET /product)
- Returns all products
- Empty list if none exist
Read by ID (GET /product/{id})
- Returns 200 OK if found
- 404 Not Found if missing
Update (PUT /product/{id})
- Replaces the full resource
- ID stays immutable
Delete (DELETE /product/{id})
- Removes the resource
- Returns 204 No Content
API Endpoints
Method Endpoint Description
GET /product Get all products
GET /product/{id} Get product by ID
POST /product Create a new product
PUT /product/{id} Update a product
DELETE /product/{id} Delete a product
Testing Options
1) Visual Studio .http file
Send requests directly from the IDE.
2) Swagger UI
Visit:
http://localhost:5210/swagger/index.html
- Interactive testing
- Auto-generated schemas
- No extra tools required
3) Postman / cURL
Perfect for manual and automated testing.
Swagger Setup (Program.cs)
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); app.UseSwagger(); app.UseSwaggerUI(); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run();
Best Practices Used
- RESTful URLs and verbs
- Strong typing with ActionResult<T>
- Correct HTTP status codes
- Named routes for maintainability
- decimal for currency
Recommended for Production
- Data validation attributes
- Async/await everywhere
- Database persistence (EF Core)
- Authentication & authorization (JWT)
- Pagination, search, filtering
- Centralized exception handling
- Logging and monitoring
Production Enhancements (What to Add Next)
- EF Core + SQL Server for persistence
- Model validation with DataAnnotations
- Pagination & search for large datasets
- JWT auth with role-based access
- Global exception filter for clean errors
Troubleshooting Tips
- 404 on /product → check port & app status
- Empty list → create data first (in-memory resets on restart)
- Swagger not loading → verify middleware order
- CORS issues → enable CORS in Program.cs
Final Thoughts
This Product CRUD API is a solid, interview-ready foundation that demonstrates how to design clean, maintainable ASP.NET Core APIs. Start with this structure, then layer in persistence, security, and scalability features as your project grows.
R
Ravi Kumar
Technical writer and software development expert at Murmu Software Infotech, sharing insights on modern web development, software architecture, and best practices.

