A production-ready library management system built with .NET 9.0, PostgreSQL, and Clean Architecture principles. Features comprehensive book management, member management, and borrowing tracking with JWT authentication, CQRS pattern, and advanced error handling.
- Features
- Architecture & Design Patterns
- Tech Stack
- Project Structure
- Prerequisites
- Setup & Run Instructions
- Sample Login Credentials
- API Workflow Documentation
- Development Notes
- Book Management:
- Full CRUD operations with automatic availability status updates
- ISBN uniqueness validation
- Cursor-based pagination for efficient data browsing
- Advanced filtering by title, category, and ISBN
- Member Management:
- Complete member lifecycle management
- Email uniqueness validation
- Soft delete support with audit trail
- Borrowing System:
- Multi-book borrowing in a single transaction
- Maximum 5 active borrows per member (enforced at business logic level)
- Real-time book availability checks (
CopiesAvailable > 0) - Automatic copy count management
- Book return functionality with availability restoration
- Borrowing summary and statistics by member
- 2-second simulated processing delay for testing async operations
- JWT Authentication: Token-based authentication with configurable expiration
- Role-Based Authorization: Admin and User roles with policy-based access control
- Password Security: BCrypt password hashing
- Secure Endpoints: All endpoints (except login) require valid JWT tokens
- Audit Trail: Automatic tracking of CreatedBy, ModifiedBy, and DeletedBy with timestamps
- Soft Delete: Logical deletion preserving data integrity and history
- Correlation IDs: Request tracking with unique identifiers for debugging
- Result Pattern: Railway-oriented programming for predictable error handling
- Domain Events: Event-driven architecture foundation
- Auto-Seeding: Default admin account creation on first run
This project demonstrates professional software architecture principles:
- Presentation Layer (
MiniLibrary.API): Minimal APIs, endpoints, middleware - Application Layer (
MiniLibrary.Application): Business logic, CQRS handlers, validators - Domain Layer (
MiniLibrary.Domain): Entities, business rules, domain events - Infrastructure Layer (
MiniLibrary.Infrastructure): Database, authentication, external services - Shared Kernel (
MiniLibrary.SharedKernel): Cross-cutting abstractions (Result, Error, Entity base classes)
- CQRS (Command Query Responsibility Segregation): Separate read and write operations for better scalability
- Decorator Pattern: Cross-cutting concerns via behavior decorators
ValidationDecorator: FluentValidation rules before executionLoggingDecorator: Structured logging with SerilogSimulatedDelayDecorator: 2-second delay for testing
- Result Pattern: Type-safe error handling without exceptions
- Repository Pattern: Data access abstraction via
IApplicationDbContext - Domain Events: Event sourcing foundation for future features
- Dependency Injection: Auto-registration via Scrutor with interface scanning
- .NET 9.0 - Modern application framework with Minimal APIs
- PostgreSQL - Relational database with advanced features
- Entity Framework Core 9.0 - ORM with migrations and fluent configuration
- C# 13 - Latest language features
- JWT Bearer Authentication - Stateless token-based auth
- BCrypt.Net-Next - Secure password hashing
- Serilog 4.3.0 - Structured logging framework
- Console + File Sinks - Real-time and persistent logging
- Correlation IDs - Request tracking across log entries
- Swagger/OpenAPI - Interactive API documentation
- Minimal APIs - Lightweight endpoint definitions
- Problem Details - RFC 7807 standard error responses
- FluentValidation 12.1.0 - Declarative validation rules
- Scrutor 6.1.0 - Assembly scanning and auto-registration
- EF Core Tools - Database migrations and scaffolding
- Health Checks - Application health monitoring
Endpoints/ - Minimal API endpoints organized by feature (Authentication, Books, Members, Borrowings)
Application/Behaviors/ - Decorator pattern implementations for cross-cutting concerns (validation, logging, delay)
Domain/Entities/ - Business entities with rich domain logic and error definitions
Infrastructure/Database/ - EF Core DbContext, migrations, configurations, and seeding logic
SharedKernel/ - Reusable abstractions shared across projects (Result pattern, base classes)
Before running the application, ensure you have the following installed:
git clone https://github.com/naimur1046/mini-library-management-system.git
cd MiniLibraryUpdate the connection string in MiniLibrary.API/appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Port=5432;Database=MiniLibraryDb;Username=postgres;Password=YourPassword"
}
}Replace YourPassword with your PostgreSQL password.
The JWT settings are pre-configured in appsettings.json. For production, update the secret key:
{
"JwtSettings": {
"Secret": "YourSuperSecretKeyThatIsAtLeast32CharactersLong!",
"Issuer": "MiniLibrary",
"Audience": "MiniLibraryUsers",
"ExpirationInMinutes": 60
}
}Create the PostgreSQL database:
CREATE DATABASE MiniLibraryDb;Navigate to the API project directory and run migrations:
cd MiniLibrary.API
dotnet ef database updateIf Entity Framework tools are not installed, install them first:
dotnet tool install --global dotnet-efdotnet buildcd MiniLibrary.API
dotnet runThe API will be available at:
- HTTPS:
https://localhost:5001 - HTTP:
http://localhost:5000 - Swagger UI:
https://localhost:5001/swagger(Development mode only)
Navigate to https://localhost:5001/swagger to explore and test the API endpoints interactively.
The system seeds a default Admin account automatically if no users exist in the database.
| Field | Value |
|---|---|
admin@minilibrary.com |
|
| Password | Admin@123 |
| Role | Admin |
POST /authentication/register
{
"fullName": "Naimur Rahman",
"email": "naimur@gmail.com",
"password": "Naimur@123#",
"role": 1
}User = 1: Regular user with read access and can create borrowingsAdmin = 2: Administrator with full access to all operations
POST /authentication/login
{
"email": "admin@minilibrary.com",
"password": "Admin@123"
}Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Use the returned token in the Authorization header for subsequent requests:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
https://localhost:5001
Creates a new user account. Only administrators can register new users.
- Endpoint:
POST /authentication/register - Authorization: AdminOnly (requires valid Admin JWT token)
- Request Body:
{
"fullName": "Naimur Rahman",
"email": "naimur@gmail.com",
"password": "Naimur@123#",
"role": 1 or 2
}- Response:
201 Created
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}Authenticates a user and returns a JWT token.
- Endpoint:
POST /authentication/login - Authorization: None
- Request Body:
{
"email": "naimur@gmail.com",
"password": "Naimur@123#"
}- Response:
200 OK
{
"token": "string"
}Retrieves a paginated list of available books with optional filters and backward and forward navigation.
-
Endpoint:
GET /books -
Authorization: UserOrAdmin policy required
-
Query Parameters:
-
Pagination
Name Type Required Description lastBookIdGUIDNo Cursor value of the last book from the previous response. sizeintNo Page size. Default: 100. Max: 1000. directionstringNo Pagination direction: "Forward"or"Backward". Default:"Forward". -
Filtering
Name Type Required Description titlestringNo Filter books by title (contains). categorystringNo Filter books by category (contains). isbnstringNo Filter books by ISBN (contains). -
Response:
200 OK
{
"books": [
{
"id": "guid",
"title": "string",
"author": "string",
"isbn": "string",
"category": "string",
"copiesAvailable": 5,
"publishedYear": 2020,
"isAvailable": true,
"createdOnUtc": "2025-01-01T10:00:00Z"
}
],
"hasMore": true
}Retrieves a specific book by its ID.
- Endpoint:
GET /books/{id} - Authorization: UserOrAdmin policy required
- Path Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
id |
GUID |
Yes | The unique identifier of the book. |
- Response:
200 OK
{
"id": "guid",
"title": "string",
"author": "string",
"isbn": "string",
"category": "string",
"copiesAvailable": 0,
"publishedYear": 0,
"isAvailable": true,
"createdOnUtc": "2025-01-01T10:00:00Z"
}Creates a new book in the library.
- Endpoint:
POST /books - Authorization: UserOrAdmin policy required
- Request Body:
{
"title": "string",
"author": "string",
"isbn": "string",
"category": "string",
"copiesAvailable": 0,
"publishedYear": 0
}- Response:
201 Created
{
"id": "guid"
}Updates an existing book. All fields should be provided in the request body.
-
Endpoint:
PUT /books/{id} -
Authorization: UserOrAdmin policy required
-
Path Parameters:
Parameter Type Description idGUID Unique identifier of the book -
Request Body:
{
"title": "string",
"author": "string",
"isbn": "string",
"category": "string",
"copiesAvailable": 0,
"publishedYear": 0
}- Response:
200 OK
{
"id": "guid"
}Soft-deletes a book from the library. The book is not permanently removed — instead
-
IsDeleted is set to true
-
IsAvailable is set to false
-
Endpoint:
DELETE /books/{id} -
Authorization: Required (Admin only)
-
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id |
GUID | Identifier of the book to delete |
- Response:
204 No ContentBook successfully deleted.
Retrieves a paginated list of all library members.
- Endpoint:
GET /members - Authorization: UserOrAdmin policy required
- Query Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
page |
int |
No | Page number. Default: 1. |
size |
int |
No | Page size. Default: 10. Max: 100. |
- Response:
200 OK
{
"members": [
{
"id": "guid",
"fullName": "string",
"email": "string",
"phone": "string",
"joinDate": "2025-01-01T00:00:00Z",
"isActive": true
}
],
"totalCount": 0,
"page": 1,
"pageSize": 10
}Retrieves a specific member by their ID.
- Endpoint:
GET /members/{id} - Authorization: UserOrAdmin policy required
- Path Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
id |
GUID |
Yes | The unique identifier of the member. |
- Response:
200 OK
{
"id": "guid",
"fullName": "string",
"email": "string",
"phone": "string",
"joinDate": "2025-01-01T00:00:00Z",
"isActive": true
}Creates a new library member.
- Endpoint:
POST /members - Authorization: UserOrAdmin policy required
- Request Body:
{
"fullName": "string",
"email": "string",
"phone": "string",
"joinDate": "2025-01-01T00:00:00Z",
"isActive": true
}- Response:
201 Created
{
"id": "guid"
}Updates an existing member.
- Endpoint:
PUT /members/{id} - Authorization: UserOrAdmin policy required
- Path Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
id |
GUID |
Yes | Member identifier |
- Request Body:
{
"fullName": "string",
"email": "string",
"phone": "string",
"joinDate": "2025-01-01T00:00:00Z",
"isActive": true
}- Response:
204 No Content
Soft-deletes a library member. The member is not permanently removed - instead, IsDeleted is set to true.
- Endpoint:
DELETE /members/{id} - Authorization: AdminOnly (requires Admin role)
- Path Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
id |
GUID |
Yes | Member identifier |
- Response:
204 No Content
Creates a new borrowing record for multiple books.
- Endpoint:
POST /borrowings - Authorization: UserOrAdmin policy required
- Request Body:
{
"memberId": "guid",
"borrowDate": "2025-01-01T00:00:00Z",
"dueDate": "2025-01-15T00:00:00Z",
"bookIds": ["guid1", "guid2"]
}- Response:
201 Created
"guid"Returns a borrowed book and updates its availability status.
- Endpoint:
POST /borrowings/{borrowId}/return/{bookId} - Authorization: UserOrAdmin policy required
- Path Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
borrowId |
GUID |
Yes | The unique identifier of the borrow |
bookId |
GUID |
Yes | The unique identifier of the book |
- Response:
200 OK
{
"id": "guid",
"message": "Book returned successfully"
}Retrieves borrowing statistics and details for a specific member.
- Endpoint:
GET /borrowings/summary - Authorization: UserOrAdmin policy required
- Query Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
memberId |
GUID |
Yes | The unique identifier of the member to get summary for |
- Response:
200 OK
{
"memberId": "guid",
"memberName": "string",
"totalBorrowings": 0,
"activeBorrowings": 0,
"returnedBorrowings": 0,
"borrowingHistory": [
{
"borrowId": "guid",
"bookId": "guid",
"bookTitle": "string",
"borrowDate": "2025-01-01T00:00:00Z",
"dueDate": "2025-01-15T00:00:00Z",
"returnDate": "2025-01-10T00:00:00Z"
}
]
}The application uses Serilog for structured logging with the following features:
- Console Sink: Real-time log output during development
- File Sink: Daily rolling logs stored in
__logs/log-{Date}.txt - Correlation IDs: Each HTTP request gets a unique identifier for tracking across log entries
- Request Logging: Automatic logging of all HTTP requests with method, path, status code, and duration
- Structured Data: Logs include structured context (user ID, email, request path)
Log levels: Information (default), Warning, Error, Fatal
The application uses the decorator pattern to add cross-cutting concerns to all command and query handlers:
- ValidationDecorator - Validates all commands/queries using FluentValidation before execution
- LoggingDecorator - Logs the start and completion of all operations with execution time
- SimulatedDelayDecorator - Adds a 2-second delay to all operations (for testing async UI behavior)
These decorators are automatically applied to all handlers via Scrutor's TryDecorate method.
The application separates read and write operations:
- Commands: Modify state (Create, Update, Delete) - return
Result<T>orResult - Queries: Read data (Get, GetById, GetSummary) - return
Result<TResponse>
Each operation has its own:
- Request DTO (Command/Query)
- Handler (implements
ICommandHandler<T>orIQueryHandler<T, TResponse>) - Validator (FluentValidation rules)
All operations return Result<T> instead of throwing exceptions:
// Success
Result<Book> result = Result.Success(book);
// Failure
Result<Book> result = Result.Failure<Book>(BookErrors.NotFound);Error handling is explicit and type-safe. Errors include:
Code: Error identifierDescription: Human-readable messageType: Error category (NotFound, Validation, Conflict, etc.)
Migrations: EF Core Code-First migrations track schema changes
- Run
dotnet ef database updateto apply migrations - Run
dotnet ef migrations add <name>to create new migrations
Seeding: Automatic admin user creation on first startup
- Email: admin@minilibrary.com
- Password: Admin@123
- Role: Admin
Soft Delete: Entities implementing ISoftDeletableEntity are never physically deleted
- Query filters automatically exclude deleted entities
- Audit trail preserved for compliance
Async/Await: All database operations use async methods to avoid thread blocking