Skip to content

πŸš€ Getting Started with NeurogliaΒΆ

Welcome to Neuroglia - a lightweight Python framework for building maintainable microservices using clean architecture principles.

🎯 What You'll Learn¢

This guide will take you from zero to your first working application in just a few minutes. By the end, you'll understand:

  • How to install Neuroglia and create your first project
  • The basics of clean architecture and why it matters
  • How to build a simple CRUD API using CQRS patterns
  • Where to go next for advanced features

New to Clean Architecture?

Don't worry! This guide assumes no prior knowledge. We'll explain concepts as we go.

⚑ Quick Installation¢

PrerequisitesΒΆ

  • Python 3.9 or higher (python3 --version)
  • pip (Python package manager)
  • Basic familiarity with Python and REST APIs

Install NeurogliaΒΆ

# [Optional] create and activate a virtual environment
mkdir getting-started
python3 -m venv venv
source venv/bin/activate

# Install the framework
pip install neuroglia-python

That's it! Neuroglia is built on FastAPI, so it will install all necessary dependencies automatically.

πŸ‘‹ Hello World - Your First ApplicationΒΆ

Let's create the simplest possible Neuroglia application to verify everything works.

Step 1: Create a Simple APIΒΆ

Create a file named main.py:

import uvicorn
from neuroglia.hosting.web import WebApplicationBuilder

# Create the application builder
builder = WebApplicationBuilder()

# Build the FastAPI application
app = builder.build()


# Add a simple endpoint
@app.get("/")
async def hello():
    return {"message": "Hello from Neuroglia!"}


# Run the application (if executed directly)
if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

Step 2: Run ItΒΆ

python main.py

Step 3: Test ItΒΆ

Open your browser to http://localhost:8080 or use curl:

curl http://localhost:8080
# Output: {"message": "Hello from Neuroglia!"}

πŸŽ‰ Congratulations! You've just built your first Neuroglia application!

What Just Happened?

WebApplicationBuilder is Neuroglia's main entry point. It sets up FastAPI with sensible defaults and provides hooks for dependency injection, middleware, and more. The .build() method creates the FastAPI app instance, and .run() starts the development server.

πŸ—οΈ Understanding Clean ArchitectureΒΆ

Before we build something more complex, let's understand why Neuroglia enforces a specific structure.

The Problem: Spaghetti CodeΒΆ

Traditional applications often mix concerns:

# ❌ Everything in one place - hard to test and maintain
@app.post("/orders")
async def create_order(order_data: dict):
    # Database access mixed with business logic
    conn = psycopg2.connect("...")
    # Business validation mixed with data access
    if order_data["total"] < 0:
        raise ValueError("Invalid total")
    # HTTP concerns mixed with everything else
    cursor.execute("INSERT INTO orders...")
    return {"id": result}

The Solution: Layered ArchitectureΒΆ

Neuroglia separates your code into clear layers:

πŸ“ your_project/
β”œβ”€β”€ api/           # 🌐 Handles HTTP requests/responses
β”œβ”€β”€ application/   # πŸ’Ό Orchestrates business operations
β”œβ”€β”€ domain/        # πŸ›οΈ Contains business rules and logic
└── integration/   # πŸ”Œ Talks to databases and external services

Key Benefit: Each layer has one job, making code easier to test, understand, and change.

The Dependency RuleΒΆ

Dependencies only flow inward β†’ toward the domain:

flowchart LR
    API[🌐 API] --> APP[πŸ’Ό Application]
    APP --> DOM[πŸ›οΈ Domain]
    INT[πŸ”Œ Integration] --> DOM

    style DOM fill:#e1f5fe,stroke:#0277bd,stroke-width:3px

Why This Matters: Your business logic (Domain) never depends on HTTP, databases, or external services. This means you can:

  • Test business logic without a database
  • Switch from PostgreSQL to MongoDB without changing business rules
  • Change API frameworks without touching core logic

πŸ• Building a Real Application: Pizza OrdersΒΆ

Let's build a simple pizza ordering system to see clean architecture in action.

Step 1: Define the DomainΒΆ

The domain represents your business concepts. Create domain/pizza_order.py:

from dataclasses import dataclass
from datetime import datetime
from uuid import uuid4

@dataclass
class PizzaOrder:
    """A pizza order - our core business entity."""
    id: str
    customer_name: str
    pizza_type: str
    size: str
    created_at: datetime

    @staticmethod
    def create(customer_name: str, pizza_type: str, size: str):
        """Factory method to create a new order."""
        return PizzaOrder(
            id=str(uuid4()),
            customer_name=customer_name,
            pizza_type=pizza_type,
            size=size,
            created_at=datetime.utcnow()
        )

    def is_valid(self) -> bool:
        """Business rule: validate order."""
        valid_sizes = ["small", "medium", "large"]
        return self.size in valid_sizes and len(self.customer_name) > 0

Domain Layer

Notice: No imports from FastAPI, no database code. Just pure Python business logic.

Step 2: Create Commands and Queries (CQRS)ΒΆ

CQRS separates write operations (Commands) from read operations (Queries).

Create application/commands.py:

from dataclasses import dataclass
from neuroglia.mediation import Command

@dataclass
class CreatePizzaOrderCommand(Command[dict]):
    """Command to create a new pizza order."""
    customer_name: str
    pizza_type: str
    size: str

Create application/queries.py:

from dataclasses import dataclass
from neuroglia.mediation import Query

@dataclass
class GetPizzaOrderQuery(Query[dict]):
    """Query to retrieve a pizza order."""
    order_id: str

CQRS Pattern

Commands change state (create, update, delete). Queries read state (get, list). Separating them makes code clearer and enables advanced patterns like event sourcing.

Step 3: Implement HandlersΒΆ

Handlers contain the logic to execute commands and queries. Create application/handlers.py:

from neuroglia.mediation import CommandHandler, QueryHandler
from application.commands import CreatePizzaOrderCommand
from application.queries import GetPizzaOrderQuery
from domain.pizza_order import PizzaOrder

# Simple in-memory storage for this example
orders_db = {}

class CreatePizzaOrderHandler(CommandHandler[CreatePizzaOrderCommand, dict]):
    """Handles creating new pizza orders."""

    async def handle_async(self, command: CreatePizzaOrderCommand) -> dict:
        # Create domain entity
        order = PizzaOrder.create(
            customer_name=command.customer_name,
            pizza_type=command.pizza_type,
            size=command.size
        )

        # Validate business rules
        if not order.is_valid():
            raise ValueError("Invalid order")

        # Store (in real app, this would use a Repository)
        orders_db[order.id] = order

        # Return result
        return {
            "id": order.id,
            "customer_name": order.customer_name,
            "pizza_type": order.pizza_type,
            "size": order.size,
            "created_at": order.created_at.isoformat()
        }


class GetPizzaOrderHandler(QueryHandler[GetPizzaOrderQuery, dict]):
    """Handles retrieving pizza orders."""

    async def handle_async(self, query: GetPizzaOrderQuery) -> dict:
        order = orders_db.get(query.order_id)
        if not order:
            return None

        return {
            "id": order.id,
            "customer_name": order.customer_name,
            "pizza_type": order.pizza_type,
            "size": order.size,
            "created_at": order.created_at.isoformat()
        }

Step 4: Create API ControllerΒΆ

Now let's expose this via HTTP. Create api/orders_controller.py:

from neuroglia.mvc import ControllerBase
from neuroglia.mediation import Mediator
from application.commands import CreatePizzaOrderCommand
from application.queries import GetPizzaOrderQuery
from classy_fastapi.decorators import get, post
from pydantic import BaseModel

class CreateOrderRequest(BaseModel):
    customer_name: str
    pizza_type: str
    size: str

class OrdersController(ControllerBase):
    """Pizza orders API endpoint."""

    @post("/", status_code=201)
    async def create_order(self, request: CreateOrderRequest):
        """Create a new pizza order."""
        command = CreatePizzaOrderCommand(
            customer_name=request.customer_name,
            pizza_type=request.pizza_type,
            size=request.size
        )
        result = await self.mediator.execute_async(command)
        return result

    @get("/{order_id}")
    async def get_order(self, order_id: str):
        """Retrieve a pizza order by ID."""
        query = GetPizzaOrderQuery(order_id=order_id)
        result = await self.mediator.execute_async(query)
        return result if result else {"error": "Order not found"}

API Layer

The controller is thin - it just translates HTTP requests to commands/queries and sends them to the Mediator.

Step 5: Wire It All TogetherΒΆ

Update your main.py:

from neuroglia.hosting.web import WebApplicationBuilder
from application.handlers import CreatePizzaOrderHandler, GetPizzaOrderHandler
from api.orders_controller import OrdersController

# Create application builder
builder = WebApplicationBuilder()

# Configure core services
Mediator.configure(builder, ["application.handlers"])
Mapper.configure(builder, ["application.dtos", "api.dtos", "domain.entities"])

# Build and configure app
app = builder.build()
app.use_controllers()

if __name__ == "__main__":
    app.run()

Step 6: Test Your APIΒΆ

Run the application:

python main.py

Create an order:

curl -X POST http://localhost:8080/orders \
  -H "Content-Type: application/json" \
  -d '{"customer_name":"John","pizza_type":"Margherita","size":"large"}'

Response:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "customer_name": "John",
  "pizza_type": "Margherita",
  "size": "large",
  "created_at": "2025-10-25T10:30:00"
}

Get the order:

curl http://localhost:8080/orders/550e8400-e29b-41d4-a716-446655440000

πŸŽ“ What You Just BuiltΒΆ

You've created a clean architecture application with:

  • βœ… Domain Layer: PizzaOrder entity with business rules
  • βœ… Application Layer: Commands, Queries, and Handlers
  • βœ… API Layer: REST controller using FastAPI
  • βœ… CQRS Pattern: Separate write and read operations
  • βœ… Dependency Injection: Automatic service resolution
  • βœ… Mediator Pattern: Decoupled command/query execution

πŸš€ What's Next?ΒΆ

For BeginnersΒΆ

  1. 3-Minute Bootstrap - See more setup options
  2. Core Concepts - Understand Clean Architecture, DDD, CQRS (coming soon)
  3. Complete Tutorial - Build full Mario's Pizzeria app (coming soon)

Learn Framework FeaturesΒΆ

Explore Advanced TopicsΒΆ

πŸ› TroubleshootingΒΆ

Common IssuesΒΆ

Q: Import errors when running the application

ModuleNotFoundError: No module named 'neuroglia'

A: Make sure Neuroglia is installed: pip install neuroglia

Q: Application won't start

Address already in use

A: Port 8080 is taken. Change the port: app.run(port=8081)

Q: Mediator can't find handlers

No handler registered for command

A: Ensure you're using Mediator.configure(builder, ["application.handlers"]) to auto-discover handlers in the specified modules.

Q: Module import errors in project

ImportError: attempted relative import with no known parent package

A: Add your project root to PYTHONPATH: export PYTHONPATH=. or run with python -m main

Getting HelpΒΆ

πŸ’‘ Key TakeawaysΒΆ

  1. Clean Architecture separates concerns into layers with clear dependencies
  2. CQRS separates writes (Commands) from reads (Queries)
  3. Mediator decouples controllers from handlers
  4. Domain Layer contains pure business logic with no external dependencies
  5. Controllers are thin - they delegate to the application layer

You're Ready!

You now understand the fundamentals of Neuroglia. Ready to explore more? Check out these complete sample applications!

🎯 Explore Sample Applications¢

Learn from complete, production-ready examples that demonstrate different architectural patterns:

🏦 OpenBank - Event Sourcing & CQRS¢

Perfect for: Financial systems, audit-critical applications, complex domain logic

A complete banking system demonstrating:

  • βœ… Event sourcing with KurrentDB (EventStoreDB)
  • βœ… Complete CQRS separation (write/read models)
  • βœ… Domain-driven design with rich aggregates
  • βœ… Read model reconciliation and eventual consistency
  • βœ… Snapshot strategy for performance optimization
  • βœ… Comprehensive domain events and integration events

When to use this pattern:

  • Applications requiring complete audit trails
  • Financial transactions and banking systems
  • Systems needing time-travel debugging
  • Complex business rules with event replay
# Quick start OpenBank
./openbank start
# Visit http://localhost:8899/api/docs

Explore OpenBank Documentation β†’


🎨 Simple UI - SubApp Pattern with JWT Auth¢

Perfect for: Internal dashboards, admin tools, task management systems

A modern SPA demonstrating:

  • βœ… FastAPI SubApp mounting (UI + API separation)
  • βœ… Stateless JWT authentication
  • βœ… Role-based access control (RBAC) at application layer
  • βœ… Bootstrap 5 frontend with Parcel bundler
  • βœ… Clean separation of concerns

When to use this pattern:

  • Internal business applications
  • Admin dashboards and management tools
  • Applications requiring different auth for UI vs API
  • Projects needing role-based permissions
# Quick start Simple UI
cd samples/simple-ui
poetry run python main.py
# Visit http://localhost:8000

Explore Simple UI Documentation β†’


πŸ• Mario's Pizzeria - Complete TutorialΒΆ

Perfect for: Learning all framework patterns, e-commerce systems

A comprehensive e-commerce platform featuring:

  • βœ… 9-part tutorial series from setup to deployment
  • βœ… Order management and kitchen workflows
  • βœ… Real-time event-driven processes
  • βœ… Keycloak authentication integration
  • βœ… MongoDB persistence with domain events
  • βœ… Complete observability setup

When to use this pattern:

  • Learning the complete Neuroglia framework
  • Building order processing systems
  • Event-driven workflows
  • Standard CRUD with business logic

Start the Tutorial Series β†’


πŸ—ΊοΈ Learning PathsΒΆ

Path 1: Quick Learner (1-2 hours)ΒΆ

  1. βœ… Complete this Getting Started guide
  2. πŸ“– Review Simple UI Sample for authentication patterns
  3. πŸ—οΈ Build a small CRUD app using what you learned

Path 2: Comprehensive Learner (1-2 days)ΒΆ

  1. βœ… Complete this Getting Started guide
  2. πŸ“š Work through Mario's Pizzeria Tutorial (9 parts)
  3. πŸ” Study Core Concepts
  4. 🏦 Explore OpenBank for advanced patterns
  5. 🎨 Review Simple UI for authentication

Path 3: Deep Dive (1 week)ΒΆ

  1. βœ… Complete Path 2
  2. πŸ“– Read all Architecture Patterns documentation
  3. πŸ”§ Study RBAC & Authorization Guide
  4. πŸ—οΈ Build a production application using learned patterns
  5. πŸ“Š Implement observability and monitoring

πŸ“š Additional ResourcesΒΆ

Framework DocumentationΒΆ

External Learning ResourcesΒΆ

Essential ReadingΒΆ

  • Clean Code by Robert C. Martin - Writing maintainable code
  • Implementing Domain-Driven Design by Vaughn Vernon - Practical DDD
  • Enterprise Integration Patterns by Gregor Hohpe - Messaging patterns
  • Building Microservices by Sam Newman - Distributed systems