π Mario's Pizzeria TutorialΒΆ
Build a complete pizza ordering system that demonstrates all of Neuroglia's features in a familiar, easy-to-understand context. This comprehensive tutorial covers clean architecture, CQRS, event-driven design, and web development.
π― What You'll Build
A complete pizzeria application with REST API, web UI, authentication, file-based persistence, and event-driven architecture.
π What You'll BuildΒΆ
By the end of this guide, you'll have a complete pizzeria application with:
- π REST API with automatic Swagger documentation
- π¨ Simple Web UI for customers and kitchen staff
- π OAuth Authentication for secure access
- πΎ File-based persistence using the repository pattern
- π‘ Event-driven architecture with domain events
- ποΈ Clean Architecture with CQRS and dependency injection
β‘ Quick SetupΒΆ
InstallationΒΆ
Project StructureΒΆ
The actual Mario's Pizzeria implementation follows clean architecture principles:
Source: samples/mario-pizzeria/
mario-pizzeria/
βββ main.py # Application entry point with DI setup
βββ api/
β βββ controllers/ # REST API endpoints
β β βββ orders_controller.py # Order management
β β βββ menu_controller.py # Pizza menu
β β βββ kitchen_controller.py # Kitchen operations
β βββ dtos/ # Data Transfer Objects
β βββ order_dtos.py # Order request/response models
β βββ menu_dtos.py # Menu item models
β βββ kitchen_dtos.py # Kitchen status models
βββ application/
β βββ commands/ # CQRS Command handlers
β β βββ place_order_command.py
β β βββ start_cooking_command.py
β β βββ complete_order_command.py
β βββ queries/ # CQRS Query handlers
β β βββ get_order_by_id_query.py
β β βββ get_orders_by_status_query.py
β β βββ get_active_orders_query.py
β βββ mapping/ # AutoMapper profiles
β βββ profile.py # Entity-DTO mappings
βββ domain/
β βββ entities/ # Domain entities
β β βββ pizza.py # Pizza entity with pricing
β β βββ order.py # Order aggregate root
β β βββ customer.py # Customer entity
β β βββ kitchen.py # Kitchen entity
β β βββ enums.py # Domain enumerations
β βββ events/ # Domain events
β β βββ order_events.py # Order lifecycle events
β βββ repositories/ # Repository interfaces
β βββ __init__.py # Repository abstractions
βββ integration/
β βββ repositories/ # Repository implementations
β βββ file_order_repository.py # File-based order storage
β βββ file_pizza_repository.py # File-based pizza storage
β βββ file_customer_repository.py # File-based customer storage
β βββ file_kitchen_repository.py # File-based kitchen storage
βββ tests/ # Test suite
βββ test_api.py # API integration tests
βββ test_integration.py # Full integration tests
βββ test_data/ # Test data storage
ποΈ Step 1: Domain ModelΒΆ
The domain entities demonstrate sophisticated business logic with real pricing calculations and type safety.
domain/entities/pizza.py (lines 1-63)
Key FeaturesΒΆ
- Size-based pricing: Small (1.0x), Medium (1.3x), Large (1.6x) multipliers
- Smart topping pricing: $2.50 per topping with proper decimal handling
- Auto-mapping decorators: Seamless conversion to/from DTOs
- Type safety: Enum-based size validation with PizzaSize enum
domain/entities/order.py (lines 1-106)
Key FeaturesΒΆ
- Status management: OrderStatus enum with PENDING β CONFIRMED β COOKING β READY workflow
- Time tracking: order_time, confirmed_time, cooking_started_time, actual_ready_time
- Business validation: Cannot modify confirmed orders, cannot confirm empty orders
- Auto-mapping decorators: Seamless conversion to/from DTOs
- Computed properties: Dynamic total_amount and pizza_count calculations
OrderStatus Enum (enums.py):
| samples/mario-pizzeria/domain/entities/enums.py | |
|---|---|
Notice how the Order entity encapsulates the business logic around order management, including validation rules and state transitions.
Domain Events (optional extension):
π― Step 2: Commands and QueriesΒΆ
Neuroglia implements the CQRS (Command Query Responsibility Segregation) pattern, separating write operations (commands) from read operations (queries).
Commands (Write Operations)ΒΆ
place_order_command.py (lines 17-29)
Command Handler Implementation (lines 31-95):
Queries (Read Operations)ΒΆ
get_order_by_id_query.py (lines 13-17)
| samples/mario-pizzeria/application/queries/get_order_by_id_query.py | |
|---|---|
Query Handler Implementation (lines 20-63):
Key CQRS FeaturesΒΆ
- Command/Query Separation: Clear distinction between write (commands) and read (queries) operations
- Auto-mapping: @map_from decorators for seamless DTO conversion
- Repository Pattern: Abstracted data access through IOrderRepository and ICustomerRepository
- Business Logic: Domain validation and business rules in command handlers
- Error Handling: Comprehensive error handling with OperationResult pattern
πΎ Step 3: File-Based RepositoryΒΆ
file_order_repository.py (lines 1-37)
Key Repository FeaturesΒΆ
- Generic Base Class: Inherits from
FileSystemRepository[Order, str]for common CRUD operations - Domain Interface: Implements
IOrderRepositoryfor business-specific methods - Status Filtering:
get_orders_by_status_async()for filtering by OrderStatus enum - Date Range Queries:
get_orders_by_date_range_async()for reporting functionality - Business Logic:
get_active_orders_async()returns orders in CONFIRMED or COOKING status - JSON Persistence: Built-in serialization through FileSystemRepository base class
- Type Safety: Strongly typed with Order entity and string keys
π Step 4: REST API ControllersΒΆ
orders_controller.py (lines 1-83)
Key Controller FeaturesΒΆ
- Full CRUD Operations: Complete order lifecycle management from creation to completion
- RESTful Design: Proper HTTP methods (GET, POST, PUT) and status codes (200, 201, 400, 404)
- Mediator Pattern: All business logic delegated to command/query handlers
- Type Safety: Strong typing with Pydantic models for requests and responses
- Error Handling: Consistent error responses using ControllerBase.error_responses
- Status Management: Multiple endpoints for different order status transitions
- Auto-mapping: Seamless DTO to command conversion using mapper.map()
- Clean Architecture: Controllers are thin orchestrators, business logic stays in handlers
π Step 5: OAuth AuthenticationΒΆ
src/infrastructure/auth.py
from typing import Optional
from fastapi import HTTPException, Depends, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from neuroglia.core import OperationResult
# Simple OAuth configuration
OAUTH_SCOPES = {
"orders:read": "Read order information",
"orders:write": "Create and modify orders",
"kitchen:manage": "Manage kitchen operations",
"admin": "Full administrative access"
}
# Simple token validation (in production, use proper OAuth provider)
VALID_TOKENS = {
"customer_token": {"user": "customer", "scopes": ["orders:read", "orders:write"]},
"staff_token": {"user": "kitchen_staff", "scopes": ["orders:read", "kitchen:manage"]},
"admin_token": {"user": "admin", "scopes": ["admin"]}
}
security = HTTPBearer()
async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)) -> dict:
"""Validate token and return user info"""
token = credentials.credentials
if token not in VALID_TOKENS:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication token",
headers={"WWW-Authenticate": "Bearer"},
)
return VALID_TOKENS[token]
def require_scope(required_scope: str):
"""Decorator to require specific OAuth scope"""
def check_scope(current_user: dict = Depends(get_current_user)):
user_scopes = current_user.get("scopes", [])
if required_scope not in user_scopes and "admin" not in user_scopes:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Insufficient permissions. Required scope: {required_scope}"
)
return current_user
return check_scope
π¨ Step 6: Simple Web UIΒΆ
src/web/static/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mario's Pizzeria</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.pizza-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
margin: 10px 0;
}
.order-form {
background: #f5f5f5;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
}
button {
background: #e74c3c;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #c0392b;
}
input,
select {
padding: 8px;
margin: 5px;
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
</head>
<body>
<h1>π Welcome to Mario's Pizzeria</h1>
<div id="menu-section">
<h2>Our Menu</h2>
<div id="menu-items"></div>
</div>
<div class="order-form">
<h2>Place Your Order</h2>
<form id="order-form">
<div>
<input type="text" id="customer-name" placeholder="Your Name" required />
<input type="tel" id="customer-phone" placeholder="Phone Number" required />
</div>
<div id="pizza-selection"></div>
<button type="submit">Place Order</button>
</form>
</div>
<div id="order-status" style="display: none;">
<h2>Order Status</h2>
<div id="status-details"></div>
</div>
<script>
// Load menu on page load
document.addEventListener("DOMContentLoaded", loadMenu);
async function loadMenu() {
try {
const response = await fetch("/api/menu");
const menu = await response.json();
displayMenu(menu);
} catch (error) {
console.error("Failed to load menu:", error);
}
}
function displayMenu(menu) {
const menuContainer = document.getElementById("menu-items");
menuContainer.innerHTML = menu
.map(
pizza => `
<div class="pizza-card">
<h3>${pizza.name}</h3>
<p>Base Price: $${pizza.base_price}</p>
<p>Prep Time: ${pizza.preparation_time_minutes} minutes</p>
<button onclick="addToOrder('${pizza.id}', '${pizza.name}')">Add to Order</button>
</div>
`
)
.join("");
}
function addToOrder(pizzaId, pizzaName) {
const selection = document.getElementById("pizza-selection");
selection.innerHTML += `
<div class="pizza-selection">
<span>${pizzaName}</span>
<select name="size">
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
</select>
<select name="toppings" multiple>
<option value="pepperoni">Pepperoni</option>
<option value="mushrooms">Mushrooms</option>
<option value="bell_peppers">Bell Peppers</option>
</select>
<input type="hidden" name="pizza_id" value="${pizzaId}">
</div>
`;
}
document.getElementById("order-form").addEventListener("submit", async e => {
e.preventDefault();
const formData = new FormData(e.target);
const order = {
customer_name: formData.get("customer-name"),
customer_phone: formData.get("customer-phone"),
pizza_items: Array.from(document.querySelectorAll(".pizza-selection")).map(item => ({
pizza_id: item.querySelector('[name="pizza_id"]').value,
size: item.querySelector('[name="size"]').value,
toppings: Array.from(item.querySelectorAll('[name="toppings"] option:checked')).map(opt => opt.value),
})),
};
try {
const response = await fetch("/api/orders", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(order),
});
const result = await response.json();
showOrderStatus(result);
} catch (error) {
alert("Failed to place order: " + error.message);
}
});
function showOrderStatus(order) {
document.getElementById("order-status").style.display = "block";
document.getElementById("status-details").innerHTML = `
<p><strong>Order ID:</strong> ${order.order_id}</p>
<p><strong>Total:</strong> $${order.total_amount}</p>
<p><strong>Estimated Ready Time:</strong> ${new Date(
order.estimated_ready_time
).toLocaleTimeString()}</p>
`;
}
</script>
</body>
</html>
π Step 7: Application SetupΒΆ
The main application file demonstrates sophisticated multi-app architecture with dependency injection configuration.
main.py (lines 1-226)
| samples/mario-pizzeria/main.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | |
Key Implementation FeaturesΒΆ
Multi-App Architecture (lines 102-125)
The application uses a sophisticated multi-app setup:
- Main App: Root FastAPI application with welcome endpoint
- API App: Dedicated backend API mounted at
/apiwith Swagger documentation - UI App: Future frontend application mounted at
/ui
Repository Registration Pattern (lines 64-82)
Uses interface-based dependency injection with file-based implementations:
| Repository Registration Pattern | |
|---|---|
Auto-Discovery Configuration (lines 84-98)
Framework components use module scanning for automatic registration:
π― Running the ApplicationΒΆ
The main entry point provides comprehensive application bootstrapping and startup logic:
Application Startup (lines 198-226)
π You're DoneΒΆ
Run your pizzeria:
Visit your application:
- Web UI: http://localhost:8000
- API Documentation: http://localhost:8000/docs
- API Endpoints: http://localhost:8000/api
π What You've BuiltΒΆ
- β Complete Web Application with UI and API
- β Clean Architecture with domain, application, and infrastructure layers
- β CQRS Pattern with commands and queries
- β Event-Driven Design with domain events
- β File-Based Persistence using the repository pattern
- β OAuth Authentication for secure endpoints
- β Enhanced Web Application Builder with multi-app support
- β Automatic API Documentation with Swagger UI
π Next StepsΒΆ
Now that you've built a complete application, explore advanced Neuroglia features:
ποΈ Architecture Deep DivesΒΆ
- Clean architecture principles and layer separation
- CQRS & Mediation - Advanced command/query patterns and pipeline behaviors
- Dependency Injection - Advanced DI patterns and service lifetimes
π Advanced FeaturesΒΆ
- Event Sourcing - Complete event-driven architecture with event stores
- Data Access - MongoDB and other persistence options beyond file storage
- MVC Controllers - Advanced controller patterns and API design
π Sample ApplicationsΒΆ
- OpenBank Sample - Banking domain with event sourcing
- API Gateway Sample - Microservice gateway patterns
- Desktop Controller Sample - Background services and system integration
π Related DocumentationΒΆ
- β‘ 3-Minute Bootstrap - Quick hello world setup
- π οΈ Local Development Setup - Complete development environment
- π― Getting Started Overview - Choose your learning path
π Congratulations!
You've built a complete, production-ready application using Neuroglia! All other documentation examples use this same pizzeria domain for consistency - you'll feel right at home exploring advanced features.