Back to Blog

Backend Using .NET

5 min read
Date
Dec 23, 2025
Description
documentation on how i build backend application using dotnet
Published
Published
Slug
backend-dotnet-documentation
i got internship at chubb recently and i have been assigned .NET and Angular tech stack.
so,today I am going to write backend application using dotnet :- Github:- GitHubGitHubGitHub - whyrupesh/secureProductInventoryService: this is full stack project using dotnet and angular frameworks.
 
💡
most of documentation here are done by AI, as it is better at styling the documentation. but most of the code written by me.

Project Name :- Product Inventory Service

Project Overview
  • Backend: ASP.NET Core Web API
  • Auth: JWT Authentication
  • DB: SQLite (as i am on mac)
  • Features:
    • Login & Register
    • JWT-secured APIs
    • Product CRUD
    • Pagination, Sorting, Filtering
    • Route protection (Auth Guards)
    • HTTP Interceptor for JWT
 
 

🏗️ Architecture (High Level)

ASP.NETCoreAPI ├──Controllers ├──Services ├──Repositories ├──EFCore(DbContext) ├──JWTAuthentication └──Role-basedAuthorization(optional)
 
 

🔐 Authentication Flow (Important)

  1. User registers → password hashed → saved in DB
  1. User logs in → JWT token issued
  1. Angular stores JWT (localStorage)
  1. HTTP Interceptor adds JWT to headers
  1. Backend validates JWT
  1. Only authenticated users can access Products
 
 
 
Lets create Backend First.

Create Backend Project

dotnet new webapi -n backend cd backend

Add NuGet Packages

dotnet add package Microsoft.EntityFrameworkCore.Sqlite dotnet add package Microsoft.EntityFrameworkCore.Tools dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer dotnet add package BCrypt.Net-Next
Since i am on Mac that’s why I here used SQLite. ( for sql server, i can haved used docker, but it would make our project more complex).
 
Package
Purpose
EFCore.Sqlite
SQLite database provider
EFCore.Tools
Migrations & database update
JwtBearer
JWT authentication
BCrypt.Net-Next
Secure password hashing
 
 
First i created model
  • User
  • Product
 
 
Then I created DTOs
  • RegisterDto
  • LoginDto
  • ProductDto
 
 
DTO stands for Data Transfer Object.
It is a simple object used to transfer data between layers of an application (for example: Controller ↔ Service ↔ Client) without exposing your internal models.
 
Then, I created two Interface
  • IAuthService → ( Register , Login ) methods.
    • IProductService → ( GetAll, GetbyId, Delete, Update, Create) methods.
 
Now, Let’s create services
  • AuthService(implementing IAuthService)
using backend.Services; using Microsoft.IdentityModel.Tokens; using backend.Data; using backend.DTOs; using backend.Models; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace backend.Services; public class AuthService : IAuthService { private readonly AppDbContext _context; private readonly IConfiguration _config; public AuthService(AppDbContext context, IConfiguration config) { _context = context; _config = config; } public string Register(RegisterDto dto) { var user = new User { Username = dto.Username, PasswordHash = BCrypt.Net.BCrypt.HashPassword(dto.Password) }; _context.Users.Add(user); _context.SaveChanges(); return GenerateToken(user); } public string Login(LoginDto dto) { var user = _context.Users.SingleOrDefault(u => u.Username == dto.Username); if (user == null || !BCrypt.Net.BCrypt.Verify(dto.Password, user.PasswordHash)) throw new Exception("Invalid credentials"); return GenerateToken(user); } private string GenerateToken(User user) { var claims = new[] { new Claim(ClaimTypes.Name, user.Username), new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()) }; var key = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(_config["Jwt:Key"])); var token = new JwtSecurityToken( issuer: _config["Jwt:Issuer"], audience: _config["Jwt:Audience"], claims: claims, expires: DateTime.UtcNow.AddHours(2), signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) ); return new JwtSecurityTokenHandler().WriteToken(token); } }
 
 
  • ProductService (implementing IProductService)
using backend.Data; using backend.DTOs; using backend.Models; namespace ProductInventoryAPI.Services; public class ProductService : IProductService { private readonly AppDbContext _context; public ProductService(AppDbContext context) { _context = context; } public IEnumerable<Product> GetAll() => _context.Products.ToList(); public Product GetById(int id) => _context.Products.FirstOrDefault(p => p.Id == id); public Product Create(ProductDto dto) { var product = new Product { Name = dto.Name, Description = dto.Description, Price = (decimal)dto.Price, Quantity = dto.Quantity, Category = dto.Category, IsActive = dto.IsActive, CreatedDate = DateTime.UtcNow }; _context.Products.Add(product); _context.SaveChanges(); return product; } public void Update(int id, ProductDto dto) { var product = GetById(id); if (product == null) return; product.Name = dto.Name; product.Description = dto.Description; product.Price = (decimal)dto.Price; product.Quantity = dto.Quantity; product.Category = dto.Category; product.IsActive = dto.IsActive; _context.SaveChanges(); } public void Delete(int id) { var product = GetById(id); if (product == null) return; _context.Products.Remove(product); _context.SaveChanges(); } }
 
 
 
Here, as you will notice we have not created AuthService or ProductService directly, rather we created interface IAuthService and IProductService and then implemented it.
“Why not just write AuthService and ProductService and use them directly?”
 

🧠 Real-World Analogy (Best Way)

Imagine this situation:

You hire a delivery person.
❌ Bad design:
“Only Ram can deliver my packages.”
If Ram is sick → business stops.
✅ Good design:
“Anyone who can deliver packages can work.”
Now Ram, Shyam, or Zomato can deliver.
👉 Interface = job role
👉 Class = employee
 

🧱 Code Example WITHOUT Interface (Bad for growth)

public class ProductsController { private ProductService _service =new ProductService(); publicvoidGet() { _service.GetAll(); } }

❌ Problems:

  • Controller depends directly on ProductService
  • You cannot swap implementation
  • Hard to test
  • Hard to extend

🧱 WITH Interface (Correct way)

public class ProductsController { private readonly IProductService _service; public ProductsController(IProductService service) { _service = service; } }

✅ Benefits:

  • Controller does NOT care how product logic works
  • You can replace implementation anytime
  • Clean separation of concerns

🧪 Testing Benefit (VERY IMPORTANT)

Suppose tomorrow you want to test controller:

public class FakeProductService :IProductService { public IEnumerable<Product>GetAll() =>new List<Product> {new Product { Name ="Test" } }; }
👉 You can inject FakeProductService
👉 No database
👉 No SQLite
👉 Fast tests
❌ Without interface → impossible

📌 Summary – Why Interface?

Reason
Benefit
Loose coupling
Components independent
Easy testing
Mock/Fake services
Easy replacement
Change logic without breaking
Clean architecture
Professional code
 
 
Here, we actually did a dependency Injection.

What is Dependency Injection (DI)?

Simple definition:

DI means you do NOT create objects yourself. Someone else gives them to you.

❌ Without Dependency Injection

public classAuthController { private AuthService _service =new AuthService(); public void Login() { } }

❌ Problems:

  • Controller tightly coupled to AuthService
  • If AuthService changes → controller breaks
  • Hard to test
  • Not scalable

✅ With Dependency Injection

public classAuthController { private readonly IAuthService _service; public AuthController(IAuthService service) { _service = service; } }

💡 Who creates AuthService?

👉 ASP.NET Core DI Container
builder.Services.AddScoped<IAuthService, AuthService>();
ASP.NET Core says:
“Whenever someone asks for IAuthService, give them AuthService.”
 
 
 

🧠 Final Mental Model (Remember This Forever)

Controller
“I don’t care HOW work is done.
Just give me someone who can do it.”
Interface
“Defines what can be done.”
Service
“Implements how it is done.”
DI
“Connects everything together.”
 
We ran data migration which will create table in sqlite table.
dotnet tool install --global dotnet-ef dotnet ef migrations add InitialCreate dotnet ef database update
✔ inventory.db created
✔ Tables created
This creates this folder as well.
notion image
 
 
 
 
 
 
 
 
Then, I created Controllers, where i wrote all the APIs which will use services that we created till now.
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using backend.DTOs; using backend.Services; namespace backend.Controllers; [Authorize] [ApiController] [Route("api/products")] public class ProductsController : ControllerBase { private readonly IProductService _service; public ProductsController(IProductService service) { _service = service; } [HttpGet] public IActionResult GetAll() => Ok(_service.GetAll()); [HttpGet("{id}")] public IActionResult Get(int id) => Ok(_service.GetById(id)); [HttpPost] public IActionResult Create(ProductDto dto) => Ok(_service.Create(dto)); [HttpPut("{id}")] public IActionResult Update(int id, ProductDto dto) { _service.Update(id, dto); return NoContent(); } [HttpDelete("{id}")] public IActionResult Delete(int id) { _service.Delete(id); return NoContent(); } }
And finally main program.cs file where i added all services and and ran the project.
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using backend.Data; using backend.Services; using System.Text; using backend.Services; var builder = WebApplication.CreateBuilder(args); // SQLite builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlite("Data Source=inventory.db")); // Services builder.Services.AddScoped<IAuthService, AuthService>(); builder.Services.AddScoped<IProductService, ProductService>(); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); // JWT builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) }; }); builder.Services.AddAuthorization(); var app = builder.Build(); app.UseSwagger(); app.UseSwaggerUI(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
run it:
dotnet build dotnet run
 
 
 
 
 
 
 
 
 
 
Now, Let’s move on to FRONTEND