A production-ready, modular RESTful API for e-commerce — featuring JWT-based authentication, the Specification & Repository patterns, Redis-powered caching & shopping baskets, full order lifecycle management, Stripe payment integration, delivery method management, global exception handling with RFC 7807 ProblemDetails, and a strict onion architecture that isolates business logic from all infrastructure concerns.
📖 Docs · 🚀 Quick Start · 🐛 Report Bug · 💡 Request Feature
- Highlights
- Architecture Overview
- Solution Structure
- Tech Stack
- API Reference
- Quick Start
- Configuration
- Design Patterns in Depth
- Error Handling
- Roadmap
- Contributing
- License
- Author
| Feature | Description |
|---|---|
| 🏛️ Clean Onion Architecture | 8 dedicated projects with strict inward-only dependency flow |
| 🔐 JWT Authentication | Full user registration, login, email check & current-user retrieval, powered by ASP.NET Core Identity + JWT Bearer tokens |
| 🛒 Basket Module (Redis) | Shopping cart persisted in Redis with create, update & delete support |
| 📦 Orders Module | Full order lifecycle — create, retrieve all orders & retrieve a single order by ID, all tied to the authenticated user |
| 🚚 Delivery Methods | Configurable shipping options served from /api/orders/DeliveryMethods (no auth required) |
| 💳 Stripe Payment | Payment intent creation & confirmation via Stripe API |
| ⚡ Redis Response Caching | Custom [RedisCache] action filter attribute for automatic response caching |
| 🛡️ Global Exception Handling | Custom middleware returning RFC 7807 ProblemDetails responses |
| 🔍 Specification Pattern | Composable, reusable query objects for filters, sorting, pagination & eager loading |
| 📦 Unit of Work + Generic Repository | Transactional consistency with a single, type-safe abstraction |
| 🗺️ AutoMapper + Custom Resolvers | Entity-to-DTO mapping with ProductPictureUrlResolver for dynamic image URLs |
| 📊 Pagination, Sorting & Filtering | Server-side pagination via PaginatedResult<T>, dynamic sorting & flexible query params |
| 🏷️ Brands & Types | Dedicated sub-endpoints to retrieve all product brands and product types |
This project follows Onion Architecture (also known as Clean Architecture), enforcing strict inward-only dependencies:
┌────────────────────────────────────────────────────┐
│ Presentation Layer │
│ (Controllers, Attributes, Action Filters) │
├────────────────────────────────────────────────────┤
│ Services Layer │
│ (AuthenticationService, OrderService, │
│ BasketService, CacheService, ProductService) │
├────────────────────────────────────────────────────┤
│ Services Abstraction Layer │
│ (Interfaces, DTOs, Service Contracts) │
├────────────────────────────────────────────────────┤
│ Persistence Layer │
│ (EF Core DbContext, Migrations, Repositories, │
│ Unit of Work, Data Seeding) │
├────────────────────────────────────────────────────┤
│ Shared Layer │
│ (Common Utilities, Constants, CommonResult) │
├────────────────────────────────────────────────────┤
│ Domain Layer │
│ (Entities, Contracts, No dependencies) │
└────────────────────────────────────────────────────┘
▲ Dependencies flow inward only ▲
E-Commerce.Web.slnx
│
├── E Commerce.Domain/
│ ├── Entities/
│ │ ├── ProductModule/ # Product, Brand, ProductType
│ │ ├── BasketModule/ # CustomerBasket, BasketItem
│ │ ├── OrderModule/ # Order, OrderItem, OrderAddress,
│ │ │ # DeliveryMethod, OrderStatus,
│ │ │ # ProductItemOrdered
│ │ └── IdentityModule/ # ApplicationUser
│ └── Contracts/ # Repository & Unit of Work interfaces
│
├── E Commerce.Persistence/
│ ├── Data/ # EF Core DbContext, migrations, seed data
│ └── Repositories/ # Generic & specific repository implementations
│
├── E Commerce.Services Abstraction/
│ ├── IProductService.cs
│ ├── IBasketService.cs
│ ├── IOrderService.cs
│ ├── IAuthenticationService.cs # ✨ NEW — login, register, email check, current user
│ └── DTOs/ # Request / Response data transfer objects
│ ├── ProductDTOs/
│ ├── BasketDTOs/
│ ├── OrderDTOs/
│ └── IdentityDTOs/ # ✨ NEW — LoginDTO, RegisterDTO, UserDTO
│
├── E Commerce.Services/
│ ├── ProductService.cs
│ ├── BasketService.cs
│ ├── OrderService.cs # ✨ NEW — full order creation & retrieval by ID
│ ├── AuthenticationService.cs # ✨ NEW — register, login, JWT generation (HMAC-SHA256)
│ ├── CacheService.cs # ✨ NEW — Redis response cache abstraction
│ ├── Specifications/ # Composable query specification objects
│ ├── MappingProfiles/ # AutoMapper profiles
│ └── Exceptions/ # Custom domain exception types
│
├── E Commerce.Presentation/
│ ├── Controllers/
│ │ ├── ApiBaseController.cs
│ │ ├── ProductsController.cs # ✨ UPDATED — now requires JWT; adds /brands & /types
│ │ ├── BasketsController.cs # ✨ UPDATED — added DELETE endpoint
│ │ ├── OrdersController.cs # ✨ NEW — create, get all, get by ID & delivery methods
│ │ └── AuthenticationController.cs # ✨ NEW — register, login, emailExists, currentUser
│ └── Attributes/
│ └── RedisCacheAttribute.cs # ✨ NEW — declarative response caching
│
├── E Commerce.Shared/
│ ├── CommonResult/ # Result<T>, Error (NotFound, Validation, InvalidCredentials)
│ └── (Shared utilities & constants)
│
└── E-Commerce.Web/
├── CustomMiddleWares/ # ✨ NEW — global exception handling middleware
├── Extensions/ # Service & middleware registration extensions
├── Factories/ # Problem details factory
├── Program.cs
├── appsettings.json
└── wwwroot/ # Static assets (product images)
| Category | Technology |
|---|---|
| Framework | ASP.NET Core 9 Web API |
| Language | C# 13 |
| ORM | Entity Framework Core 9 |
| Database | SQL Server |
| Cache / Basket Store | Redis (StackExchange.Redis) |
| Authentication | ASP.NET Core Identity + JWT Bearer (HMAC-SHA256) |
| Object Mapping | AutoMapper |
| API Documentation | Swagger / OpenAPI (Swashbuckle) |
| Architecture | Clean Onion Architecture |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/authentication/register |
❌ | Register a new user; returns UserDTO with JWT |
POST |
/api/authentication/login |
❌ | Login and receive a JWT token |
GET |
/api/authentication/emailExists?email= |
❌ | Check whether an email address is already registered |
GET |
/api/authentication/CurrentUser |
✅ JWT | Get the current authenticated user's profile |
UserDTO response:
{
"email": "user@example.com",
"displayName": "John Doe",
"token": "<jwt-token>"
}| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/products |
✅ JWT | Get all products (paginated, sortable, filterable) — cached via Redis |
GET |
/api/products/{id} |
❌ | Get a single product by ID |
GET |
/api/products/brands |
❌ | Get all available product brands |
GET |
/api/products/types |
❌ | Get all available product types |
Query Parameters for GET /api/products:
| Parameter | Type | Description |
|---|---|---|
pageIndex |
int |
Page number (default: 1) |
pageSize |
int |
Items per page (default: 6) |
sort |
string |
Sort field (e.g. priceAsc, priceDesc, nameAsc) |
brandId |
int? |
Filter by brand ID |
typeId |
int? |
Filter by type ID |
search |
string? |
Search by product name |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/baskets?id= |
❌ | Get a basket by its Redis key |
POST |
/api/baskets |
❌ | Create or update a basket |
DELETE |
/api/baskets/{id} |
❌ | Delete a basket by ID |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/orders |
✅ JWT | Create a new order from the authenticated user's basket |
GET |
/api/orders |
✅ JWT | Get all orders for the authenticated user |
GET |
/api/orders/{id} |
✅ JWT | Get a specific order by its GUID |
GET |
/api/orders/DeliveryMethods |
❌ | Get all available delivery methods |
OrderDTO request body:
{
"basketId": "basket-123",
"deliveryMethodId": 1,
"shippingAddress": {
"firstName": "John",
"lastName": "Doe",
"street": "123 Main St",
"city": "Cairo",
"country": "Egypt"
}
}| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/payments/{basketId} |
✅ JWT | Create or update a Stripe payment intent for a basket |
1. Clone the repository
git clone https://github.com/Jaser1010/E-Commerce.Web.git
cd E-Commerce.Web2. Start Redis (using Docker)
docker run -d --name redis-ecommerce -p 6379:6379 redis:alpine3. Configure the application (appsettings.Development.json)
{
"ConnectionStrings": {
"DefaultConnection": "Server=.;Database=ECommerceDB;Trusted_Connection=True;",
"RedisConnection": "localhost:6379"
},
"JWTOptions": {
"SecretKey": "<your-secret-key-32-chars-min>",
"Issuer": "ECommerceApp",
"Audience": "ECommerceUsers"
},
"StripeSettings": {
"SecretKey": "<your-stripe-secret-key>"
}
}4. Apply database migrations
dotnet ef database update --project "E Commerce.Persistence" --startup-project "E-Commerce.Web"5. Run the application
dotnet run --project "E-Commerce.Web"Swagger UI is available at: https://localhost:<port>/swagger
| Key | Description |
|---|---|
ConnectionStrings:DefaultConnection |
SQL Server connection string |
ConnectionStrings:RedisConnection |
Redis server address (e.g. localhost:6379) |
JWTOptions:SecretKey |
Secret key for signing JWT tokens (min 32 characters) |
JWTOptions:Issuer |
JWT token issuer |
JWTOptions:Audience |
JWT token audience |
StripeSettings:SecretKey |
Stripe secret API key |
Query objects (e.g. ProductWithBrandAndTypeSpecification) encapsulate filtering, sorting, pagination and eager loading:
// Composable, reusable, testable
var spec = new ProductWithBrandAndTypeSpecification(queryParams);
var products = await _unitOfWork.Repository<Product>().GetAllWithSpecAsync(spec);A single IUnitOfWork gives type-safe access to all repositories and coordinates transactions:
var order = await _unitOfWork.Repository<Order>().GetByIdAsync(id);
await _unitOfWork.CompleteAsync();All service methods return Result<T> — eliminating exceptions for expected failure scenarios:
// In AuthenticationService:
if (user is null)
return Error.NotFound("User.NotFound", $"No user with email {email} was found");
return new UserDTO(user.Email!, user.DisplayName, token);Decorate any action with [RedisCache] to automatically cache the response:
[Authorize]
[HttpGet]
[RedisCache]
public async Task<ActionResult<PaginatedResult<ProductDTO>>> GetAllProducts(...)All errors are returned as RFC 7807 ProblemDetails via the global exception-handling middleware:
{
"statusCode": 404,
"message": "No User With Email user@example.com Was Found"
}Authentication & validation errors use the Result<T> pattern with typed Error codes:
| Error Type | HTTP Status | Example |
|---|---|---|
NotFound |
404 | User or resource not found |
InvalidCredentials |
401 | Wrong email or password |
Validation |
400 | Identity validation errors on register |
- Clean Onion Architecture
- Products & Baskets
- Redis Caching
- Global Exception Handling
- JWT Authentication (register, login, email check, current user)
- Product Brands & Types endpoints
- Full Orders Module (create, get all, get by ID)
- Delivery Methods
- Basket Delete endpoint
- Result Pattern (CommonResult)
- Stripe Payment Integration
- User address management
- Real-time order status (SignalR)
- Admin product management (CRUD)
Contributions are welcome! Please open an issue first to discuss what you'd like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License.