KAHIBARO
Discord Login Register

Monolithic Architecture

Key Ideas Before We Dive In

In this chapter you will understand what a monolithic architecture is and when it makes sense to use it in backend development.

You will see:

You already know what backends and REST APIs are from earlier chapters, so we will not repeat that. We will focus only on what is specific to monolithic architecture.

Key idea: A monolith is a single deployable application that contains all backend features in one codebase, one process, and usually one database.


What Is a Monolithic Architecture?

The basic definition

A monolithic architecture is a backend application where:

Imagine an e-commerce backend that handles:

In a monolith, all of these are part of a single application. You might have different modules or packages inside the codebase, but they are compiled, run, and deployed together.

Simple visualization

Monolithic backend:

text
Client (browser / mobile)
          |
          v
    [ Monolithic API ]
          |
          v
      [ Database ]

Everything, like /login, /products, /cart, /orders, is handled by that single API application.


How a Monolith Looks in Code

Example project structure

Here is what a simple monolithic FastAPI project might look like:

text
myshop/
  app/
    __init__.py
    main.py
    auth/
      __init__.py
      routes.py
      services.py
      models.py
    products/
      __init__.py
      routes.py
      services.py
      models.py
    orders/
      __init__.py
      routes.py
      services.py
      models.py
    admin/
      __init__.py
      routes.py
      services.py
      models.py
    core/
      config.py
      database.py
      security.py
  tests/
    test_auth.py
    test_products.py
    test_orders.py
  requirements.txt
  Dockerfile

This is still a single application:

You might have multiple Python modules, but from the outside, it is still one monolith.

One process, many features

In production, you might start several instances of the same monolith for load balancing, but each instance still contains all features:

text
          +----------------------+
Client -->|  Monolith instance 1 |
Client -->|  Monolith instance 2 |
Client -->|  Monolith instance 3 |
          +----------------------+
                    |
                    v
               [ Database ]

This is still a monolithic architecture, just scaled horizontally.


Typical Monolithic Flow

Let us walk through a simple example: creating an order in a monolithic ecommerce backend.

  1. Client sends POST /orders with cart items.
  2. The monolithic API receives the request in one app.
  3. Inside the same codebase, it:
    • Validates the user session (auth module)
    • Reads product data (products module)
    • Calculates prices and discounts (orders module)
    • Creates an order record in the database (orders module)
    • Calls payment service code (payments module)
  4. Returns a response with the new order.

All these steps happen in one application process, often in one database transaction.

Pseudo code:

python
@router.post("/orders")
def create_order(order_input: OrderCreate, user: User = Depends(get_current_user)):
    cart_items = get_cart_items(user.id)                  # carts module
    products = product_repository.get_by_ids(cart_items)  # products module
    total = pricing_service.calculate_total(products)     # orders module
    payment_result = payment_gateway.charge(
        user.payment_method,
        amount=total
    )                                                     # payments module
    order = order_service.create_order(
        user=user,
        products=products,
        payment=payment_result
    )                                                     # orders module
    return order

Even though we call different modules, it is still one application.


Advantages of Monolithic Architecture

Monoliths have several strong advantages, especially for beginners and small teams.

1. Simple to understand and start with

You only have:

For a beginner, this is much easier than managing many small services, each with their own configs, deployments, and databases.

Example setup:

bash
# Run the whole backend
uvicorn app.main:app --reload

Everything is up: auth, products, orders, admin.

2. Easy to develop locally

You can:

No need to simulate many microservices or local queues.

Table: Local development comparison

AspectMonolithMicroservices (later)
Repositories1Many
Services to run1 backend + maybe DBMany backends + DB + queue + more
DebuggingSingle processMultiple processes / network hops
Initial complexityLowHigh

3. Simple deployment

You typically deploy:

Example Docker Compose for a monolith:

yaml
version: "3.8"
services:
  backend:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/myshop
  db:
    image: postgres:16
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=myshop

You deploy backend and db. That is all.

4. Strong consistency is easier

Since everything is in one app and one database, it is easier to:

For example, a single transaction can update both orders and inventory tables:

python
with db_session() as session:
    order = create_order(session, user, items)
    reduce_inventory(session, items)
    session.commit()

No cross-service network calls are needed.

5. Good for small or early-stage products

When you are:

Then a monolith is usually the best choice.

You can move fast and change requirements quickly because everything is in one place.


Disadvantages and Pain Points

As a monolith grows, certain problems tend to appear.

1. Tight coupling

Features can become tightly coupled, which means:

Example of tight coupling:

python
# orders/services.py
from app.products.services import get_product_discount
from app.auth.services import get_user_level
def calculate_order_price(user_id, product_ids):
    user_level = get_user_level(user_id)
    discounts = [get_product_discount(pid, user_level) for pid in product_ids]
    ...

If get_user_level changes or moves, many parts might break.

Over time you risk creating a "big ball of mud", where everything depends on everything else.

2. Harder to scale specific parts

You can scale a monolith horizontally by running more instances. But you cannot easily scale only the part that needs it.

Example:

With a monolith, you may need to scale the entire thing just because one area is heavy. This can waste resources.

Microservices solve this by scaling only the specific service, but that brings other complexity (later chapter).

3. Slower deployments as the app grows

Since everything is packaged and deployed as one unit:

Example scenario:

4. Technology lock-in

In a monolith, all modules usually share:

If you want to introduce a new language or framework, it is much harder than in a microservices architecture.

Example:

5. Codebase complexity

Over time, if not structured well:

This is where concepts like modular monoliths and layered architecture help by organizing a monolith properly. You will see those in separate chapters.


Monolith vs Microservices at a Glance

You will learn microservices later in detail. For now, compare the high-level differences.

AspectMonolithMicroservices
Deployment unitOne appMany small services
Data storageUsually one main DBMany databases or schemas possible
Initial complexityLowHigh
Team size fitSmall teams / early stageLarger teams / complex orgs
ScalingWhole appPer service
Tech stackMostly uniformDifferent stacks per service possible
Dev environmentSimpleComplex (many services to run)
Failure isolationLowHigher (a service can fail independently)

Rule of thumb: Start with a monolith, structure it well, and only consider microservices when you clearly need them and have the experience and tooling to handle the extra complexity.


Common Misconceptions About Monoliths

Misconception 1: “Monoliths are always bad”

Reality:

The problem is not "monolith" itself, but poor internal architecture.

Misconception 2: “Monoliths cannot scale”

Reality:

Scaling example with Docker:

bash
# Scale monolith to 4 containers (depending on your tool)
docker compose up --scale backend=4

Misconception 3: “Using multiple modules means microservices”

Multiple modules or packages in the same codebase do not make it microservices. Microservices are separate deployable services that communicate over the network.

If everything is:

then it is still a monolith, even if internally it is modular.


When Should You Choose a Monolithic Architecture?

Monoliths are usually the right choice when:

Some good fit examples:

In later chapters, you will see how to structure a monolith well:

Typical Monolithic Example in Practice

Let us put the concepts into a very small example. Imagine this simple FastAPI monolithic app:

python
# app/main.py
from fastapi import FastAPI
from app.auth.routes import router as auth_router
from app.todos.routes import router as todos_router
app = FastAPI()
app.include_router(auth_router, prefix="/auth")
app.include_router(todos_router, prefix="/todos")

Authentication module:

python
# app/auth/routes.py
from fastapi import APIRouter, Depends
from .schemas import UserCreate, UserOut
from .services import register_user, get_current_user
router = APIRouter()
@router.post("/register", response_model=UserOut)
def register(user: UserCreate):
    return register_user(user)
@router.get("/me", response_model=UserOut)
def me(current_user = Depends(get_current_user)):
    return current_user

Todos module:

python
# app/todos/routes.py
from fastapi import APIRouter, Depends
from .schemas import TodoCreate, TodoOut
from .services import create_todo, list_todos
from app.auth.services import get_current_user
router = APIRouter()
@router.post("/", response_model=TodoOut)
def create(todo: TodoCreate, user = Depends(get_current_user)):
    return create_todo(user.id, todo)
@router.get("/", response_model=list[TodoOut])
def all(user = Depends(get_current_user)):
    return list_todos(user.id)

Everything is:

That is a simple monolithic backend.


How Monoliths Evolve

A realistic path for many backends:

  1. Simple monolith
    • A single codebase, minimal structure, all logic in routes and a few helpers.
  2. Better-structured monolith
    • Separate layers: routes, services, repositories
    • Separate modules for different domains: auth, products, orders, etc.
  3. Modular monolith (later chapter)
    • Strong boundaries between modules
    • Clear interfaces between domains
    • Limited cross-module dependencies
  4. Partial extraction to services
    • Some parts that need independent scaling or technology changes may be moved into separate services if really needed (microservices, event-driven architecture, etc).

You should focus on steps 1 to 3 first. Jumping directly to microservices often creates unnecessary complexity.


Summary

In the next chapters on architecture, you will compare monoliths with modular monoliths and microservices and learn how to choose and design the right architecture for your backend.

Views: 6

Comments

Please login to add a comment.

Don't have an account? Register now!