A production-grade subscription management REST API built with .NET 10, PostgreSQL, and Docker.
- Authentication & Authorization — JWT-based auth with role-based access control (User/Admin)
- Customer Management — Full CRUD with soft delete
- Subscription Plans — Create and manage plans (Basic, Premium, Enterprise)
- Subscription Module — Subscribe, renew, cancel with business rule enforcement
- Global Exception Handling — Standardized error responses via custom middleware
- Validation — FluentValidation with automatic request validation
- Logging — Structured logging with Serilog (console + rolling file)
- Pagination & Filtering — Search, sort, and paginate all list endpoints
- API Documentation — Interactive Scalar UI with JWT support
| Technology | Purpose |
|---|---|
| .NET 10 | Web framework |
| PostgreSQL | Database |
| Entity Framework Core | ORM |
| JWT Bearer | Authentication |
| FluentValidation | Request validation |
| Serilog | Structured logging |
| Scalar | API documentation |
| Docker | Containerization |
src/
├── Controllers/ # API endpoints
├── Services/ # Business logic
├── Repositories/ # Data access layer (generic repository)
├── Models/ # Database entities
├── DTOs/ # Request/response objects
├── Validators/ # FluentValidation validators
├── Middleware/ # Exception handling, validation filter
├── Exceptions/ # Custom exception types
├── Data/ # DbContext and EF Core config
└── Program.cs # Application entry point
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/auth/register |
No | Register a new user |
| POST | /api/auth/login |
No | Login and receive JWT token |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/customers |
User | Create a customer |
| GET | /api/customers |
User | List customers (paginated) |
| GET | /api/customers/{id} |
User | Get customer by ID |
| PUT | /api/customers/{id} |
User | Update customer |
| DELETE | /api/customers/{id} |
User | Soft delete customer |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/plans |
Admin | Create a plan |
| GET | /api/plans |
Public | List plans (paginated) |
| GET | /api/plans/{id} |
User | Get plan by ID |
| PUT | /api/plans/{id} |
Admin | Update plan |
| DELETE | /api/plans/{id} |
Admin | Deactivate plan |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/subscriptions |
User | Subscribe customer to plan |
| POST | /api/subscriptions/{id}/renew |
User | Renew subscription |
| POST | /api/subscriptions/{id}/cancel |
User | Cancel subscription |
| GET | /api/subscriptions |
User | List all (paginated/filtered) |
| GET | /api/subscriptions/active |
User | Get active subscriptions |
| GET | /api/subscriptions/customer/{id} |
User | Get customer's subscriptions |
docker-compose up --buildThe API will be available at http://localhost:8080.
-
Start PostgreSQL (or use Docker for just the DB):
docker-compose up db
-
Set user secrets:
dotnet user-secrets set "Jwt:Key" "your-secret-key-at-least-32-chars" dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Host=localhost;Database=subscriptiondb;Username=postgres;Password=subscription1234"
-
Run migrations:
dotnet ef database update
-
Run the application:
dotnet run
The API will be available at http://localhost:5108.
Scalar UI is available in development mode at:
http://localhost:5108/scalar/v1
| Variable | Description |
|---|---|
ConnectionStrings__DefaultConnection |
PostgreSQL connection string |
Jwt__Key |
Secret key for JWT signing (min 32 chars) |
Jwt__Issuer |
JWT issuer |
Jwt__Audience |
JWT audience |
ASPNETCORE_ENVIRONMENT |
Set to Development for Scalar UI |
Request → ExceptionMiddleware → Serilog → ValidationFilter → Controller → Service → Repository → DbContext → PostgreSQL
- Repository Pattern — Abstracts data access for testability
- Custom Exceptions —
NotFoundException,ConflictException,AppExceptionmap to HTTP status codes - Global Exception Middleware — Eliminates try/catch in controllers; produces consistent error responses
- Soft Delete — Customers are never permanently deleted;
IsDeletedflag with query filter - Renewal Chain —
RenewedFromIdtracks subscription renewal history
User (1) ←→ (1) Customer
Customer (1) ←→ (Many) CustomerSubscription
SubscriptionPlan (1) ←→ (Many) CustomerSubscription
CustomerSubscription → (self) RenewedFrom
All errors return a consistent JSON structure:
{
"statusCode": 404,
"message": "Customer with ID 99 not found",
"detail": null,
"timestamp": "2026-05-18T10:00:00Z",
"traceId": "0HN4ABC123..."
}{
"data": [],
"page": 1,
"pageSize": 10,
"totalCount": 25,
"totalPages": 3,
"hasPrevious": false,
"hasNext": true
}- Customers cannot have duplicate active subscriptions to the same plan
- Only active or expired subscriptions can be renewed
- Cancelled subscriptions cannot be renewed
- Plans must be active to subscribe or renew
- Renewal starts from the current subscription's end date (or now if expired)
- Only admins can create, update, or deactivate plans