A .NET 8 Web API for managing grocery orders and delivery scheduling. The core challenge: dynamically generating available delivery time slots per product category, with hard capacity constraints enforced through domain logic — not database triggers or manual checks.
- Dynamic Slot Generation — Delivery time slots are auto-generated based on each product category's allowed days and time windows — no hardcoded slots
- Capacity Enforcement — Slots with 4+ scheduled deliveries are automatically marked unavailable; enforced in domain logic, not the UI
- CQRS with MediatR — All operations flow through command/query handlers, keeping controllers thin and logic testable
- Clean Architecture — Domain, Application, Core, Infrastructure, and API layers fully separated
- Unit Tested — Application layer covered with xUnit tests
- Vanilla JS Frontend — Lightweight frontend included for demo purposes
Src/
├── Grocery_Store_Task_API/ # Presentation Layer — Controllers, Program.cs
├── Grocery_Store_Task_APPLICATION/ # Application Layer — CQRS Handlers, DTOs, Validators
├── Grocery_Store_Task_CORE/ # Core Layer — Shared interfaces and abstractions
├── Grocery_Store_Task_DOMAIN/ # Domain Layer — Entities, Enums, Business Rules
├── Grocery_Store_Task_INFRASTRUCTURE/ # Infrastructure Layer — EF Core, Repositories
Tests/
└── Grocery_Store_Task_APPLICATIONTests/ # xUnit tests for Application layer
GroceryStoreTaskFrontend/ # Vanilla JS/HTML/CSS demo frontend
How a request flows:
- HTTP request hits a Controller (API layer)
- Controller sends a Command or Query via MediatR
- The appropriate Handler in the Application layer processes it
- Handler calls Domain logic and Repository interfaces
- Infrastructure layer executes database operations
- Result returns back up through the layers
The interesting domain challenge here is slot generation. Each product category defines:
- Allowed delivery days (e.g., Monday, Wednesday, Friday)
- Time windows (e.g., 9am–12pm, 2pm–6pm)
- Capacity limit (max 4 deliveries per slot)
When a customer browses available slots, the system dynamically generates valid slots for the next N days based on the category's rules, then filters out any that are already at capacity. No slots are pre-stored — they're computed on demand.
| Category | Technology |
|---|---|
| Framework | ASP.NET Core Web API (.NET 8) |
| Pattern | CQRS + MediatR |
| ORM | Entity Framework Core |
| Database | SQL Server |
| Testing | xUnit |
| Frontend | Vanilla JavaScript, HTML, CSS |
| API Docs | Swagger / Swashbuckle |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/Products |
Get all products with their categories |
GET |
/api/Products/{id} |
Get product by ID |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/Slots/available/{categoryId} |
Get available slots for a product category |
POST |
/api/Slots/book |
Book a delivery slot |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/Orders |
Create a new order with a booked slot |
GET |
/api/Orders/{id} |
Get order details |
- .NET 8 SDK
- SQL Server
-
Clone the repository
git clone https://github.com/AhmedMTwab/GroceryStoreTask.git cd GroceryStoreTask -
Configure the connection string in
Src/Grocery_Store_Task_API/appsettings.json{ "ConnectionStrings": { "DefaultConnection": "Server=.;Database=GroceryStoreDb;Trusted_Connection=True;" } }📌 Database Connection Note: This application is connected to a deployed database, so you don't need to change the connection string. However, if the deployed database fails or you want to use your own database, you can update the connection string above.
-
Apply migrations
dotnet ef database update --project Src/Grocery_Store_Task_INFRASTRUCTURE
-
Run the API
dotnet run --project Src/Grocery_Store_Task_API
-
Open the frontend — open
GroceryStoreTaskFrontend/index.htmlin your browser, or access Swagger athttps://localhost:5001
dotnet test Tests/Grocery_Store_Task_APPLICATIONTests


