Backend Architecture Concepts
What You'll Build Today
Welcome to Day 19. Today marks a significant shift in your journey. Until now, you have been writing scripts that run on your computer, do a task, and then stop. This is great for automation, but it is not how the internet works, and it is certainly not how modern AI applications work.
Today, we are stepping away from pure syntax and entering the world of Architecture. We are going to build a Server Simulator.
We won't be writing a real web server (that's tomorrow!). Instead, we will write a Python program that simulates how a backend system functions. You will build a class that accepts "requests," routes them to the correct logic, checks for security, and returns a "response."
Here is what you will learn and why:
* The Server Concept: You will learn that a server is just a computer program that never stops running, waiting to help users. This is essential for hosting your AI models so others can use them.
* Request/Response Cycle: You will understand the universal language of the web. Without this, your frontend cannot talk to your AI backend.
* Routing: You will build a traffic control system that decides whether a user wants to login, chat, or save data. This organizes your code into logical sections.
* Middleware: You will create "guards" that inspect data before it reaches your core logic. This is critical for security and error handling in AI apps.
* Statelessness: You will learn why your server shouldn't remember specific users in its own memory, a concept vital for scaling your app to millions of users.
The Problem
Let's look at the way we have been writing code so far. Imagine you have built a fantastic AI career coach using the skills from Phase 1. You want your friend to use it.
Here is the code you send them:
# simple_coach.py
import time
def get_ai_response(message):
# Pretend this calls an LLM
time.sleep(1)
return f"That's interesting! Tell me more about '{message}'."
history = []
print("--- AI Career Coach (Local Version) ---")
while True:
user_input = input("You: ")
if user_input.lower() == "quit":
break
history.append(user_input)
response = get_ai_response(user_input)
print(f"Coach: {response}")
This works fine for you. But here is the pain:
history list lives inside Friend A's computer memory. When they close the script, it's gone.We need a Backend Architecture. We need to take the logic (the "Brain") off the user's computer and put it on a central computer (the "Server"). The user's computer (the "Client") just sends messages and displays answers.
But how does the Server know which function to run? How does it handle multiple people? Let's build a simulation to find out.
Let's Build It
We are going to simulate a backend. We will write a Python class that acts like a web server.
Step 1: The "Forever" Loop
A server is different from a script because it waits. It listens.
Let's create a MockServer class. In a real scenario, this would listen to network ports. In our simulation, it will listen to our typed commands.
import time
class MockServer:
def __init__(self):
self.is_running = True
print("Server started. Waiting for requests...")
def start(self):
# This simulates the server's main loop
while self.is_running:
# In real life, this comes from the internet
# Here, we simulate it with input
raw_request = input("\n[Incoming Traffic] Enter command (path:data): ")
if raw_request == "shutdown":
self.is_running = False
print("Server shutting down...")
break
# Process the request
response = self.handle_request(raw_request)
print(f"[Server Response] {response}")
def handle_request(self, raw_data):
return "200 OK: I heard you."
# Run the server
server = MockServer()
server.start()
Why this matters:
Run this code. It doesn't stop after one task. It keeps asking for input. This is the heartbeat of backend architecture. The handle_request method is currently useless, but it proves the server is "alive."
Step 2: Structured Requests (The Protocol)
Computers need structure. Sending a string like "login user1" is messy. The web uses a specific structure (HTTP). We will simulate this with a Python Dictionary.
A request usually has:
/chat, /login).Let's upgrade our server to parse a simulated request string.
import json
class MockServer:
def __init__(self):
self.is_running = True
def parse_request(self, raw_input):
# We expect input in format: "METHOD /path data"
# Example: "POST /chat Hello AI"
try:
parts = raw_input.split(" ", 2)
if len(parts) < 2:
return None
method = parts[0]
path = parts[1]
body = parts[2] if len(parts) > 2 else ""
# This dictionary IS the "Request" object
return {
"method": method,
"path": path,
"body": body
}
except:
return None
def start(self):
print("Server ready. Try: 'POST /chat Hello'")
while self.is_running:
raw_input = input("\n> ")
if raw_input == "EXIT":
break
request_obj = self.parse_request(raw_input)
if request_obj:
print(f"Processing {request_obj['method']} request to {request_obj['path']}")
# We will add logic here next
else:
print("400 Bad Request: Invalid format")
server = MockServer()
server.start()
Why this matters:
You defined a protocol. Both the sender and receiver agree on the format. If you type "Hello", it fails. If you type "POST /chat Hello", it works. This strictness allows systems to scale.
Step 3: Routing (The Traffic Cop)
Now the server receives the request, but it doesn't know what to do with it. We need a Router. A router looks at the path and decides which function to call. These functions are often called "Endpoints" or "Controllers."
class MockServer:
def __init__(self):
self.is_running = True
# A simple simulated database
self.users = ["alice", "bob"]
def parse_request(self, raw_input):
# Same parser as Step 2
parts = raw_input.split(" ", 2)
if len(parts) < 2: return None
return {"method": parts[0], "path": parts[1], "body": parts[2] if len(parts) > 2 else ""}
# --- ENDPOINTS (The Logic) ---
def endpoint_chat(self, body):
return f"AI Response: You said '{body}', and that is fascinating."
def endpoint_users(self):
return f"Active users: {', '.join(self.users)}"
def endpoint_not_found(self):
return "404 Error: Path not found."
# --- THE ROUTER ---
def router(self, request):
path = request["path"]
method = request["method"]
# Routing Logic
if path == "/chat" and method == "POST":
return self.endpoint_chat(request["body"])
elif path == "/users" and method == "GET":
return self.endpoint_users()
else:
return self.endpoint_not_found()
def start(self):
print("Server ready. Try: 'GET /users' or 'POST /chat hello'")
while self.is_running:
raw_input = input("\n> ")
if raw_input == "EXIT": break
request = self.parse_request(raw_input)
if request:
response = self.router(request)
print(f"SERVER SAYS: {response}")
else:
print("Invalid format")
server = MockServer()
server.start()
Why this matters:
You have separated the network handling (input/parsing) from the business logic (endpoints). If you want to add a new feature, you just add a new elif in the router and a new endpoint function. You don't have to rewrite the whole server.
Step 4: Middleware (The Security Guard)
What if we only want "alice" to be able to use the chat? We shouldn't check for "alice" inside the endpoint_chat function. Why? Because then we'd have to copy that check into every single function we write.
Instead, we use Middleware. Middleware sits between the incoming request and the router. It intercepts the request, checks it, and either rejects it or passes it along.
class MockServer:
def __init__(self):
self.is_running = True
self.banned_words = ["spam", "virus"]
def parse_request(self, raw_input):
parts = raw_input.split(" ", 2)
if len(parts) < 2: return None
return {"method": parts[0], "path": parts[1], "body": parts[2] if len(parts) > 2 else ""}
# --- MIDDLEWARE ---
def security_middleware(self, request):
# 1. Check for banned content
for word in self.banned_words:
if word in request["body"]:
return False, "403 Forbidden: Banned content detected"
# 2. Logging (Side effect)
print(f"[LOG] Request received for {request['path']}")
return True, "OK"
# --- ROUTER ---
def router(self, request):
if request["path"] == "/chat":
return f"AI Response: Processing '{request['body']}'"
return "404 Not Found"
def start(self):
print("Server ready. Try: 'POST /chat hello' or 'POST /chat spam'")
while self.is_running:
raw_input = input("\n> ")
if raw_input == "EXIT": break
request = self.parse_request(raw_input)
if request:
# RUN MIDDLEWARE FIRST
is_safe, message = self.security_middleware(request)
if is_safe:
# If safe, proceed to router
response = self.router(request)
print(f"SERVER SAYS: {response}")
else:
# If unsafe, reject immediately
print(f"SECURITY BLOCK: {message}")
server = MockServer()
server.start()
Why this matters:
Try typing POST /chat this is spam. The router never even sees that request. The middleware killed it. This is how we handle authentication (logging in), rate limiting (preventing abuse), and logging without cluttering our main AI code.
Now You Try
You have the skeleton of a backend. Now expand it.
Add a new endpoint logic and a route in the router for DELETE /history. It should return "Database wiped."
Modify the request parser to look for a user header (simulate this by typing POST /chat user=admin hello).
Add middleware that checks: If the path is /history AND the user is NOT "admin", return "401 Unauthorized."
Add a route /crash. In the endpoint function, raise a generic Python Exception("Server died").
Wrap your router call in a try/except block inside the start loop. If an error occurs, the server should not stop running. It should print "500 Internal Server Error" and ask for the next request. This is how real servers stay alive even when code breaks.
Challenge Project: The RAG Architecture Designer
We cannot draw diagrams in code, but we can describe data flow. Your challenge is to write a script that validates the architecture of a Multi-User RAG (Retrieval Augmented Generation) System.
The Scenario:You are building a system where:
Create a Python script that defines a class RAGSystem.
* It should have methods for each component: frontend(), api_gateway(), vector_db(), llm_provider().
* The frontend sends a dictionary {"query": "..."} to the api_gateway.
* The api_gateway passes it to the backend.
* The backend must call vector_db to get "context" (just return a dummy string).
* The backend must combine the query + context and send it to llm_provider.
* Print the entire journey of the data using print statements like [Frontend] -> Sending to API....
[User] Types: "How do I fix my printer?"
[Frontend] Sending request to API Gateway...
[API Gateway] Routing to Backend Service...
[Backend] Querying Vector DB for 'printer fix'...
[Vector DB] Found 2 documents. Returning to Backend.
[Backend] Constructing prompt with context. Sending to LLM...
[LLM] Generated answer.
[Backend] Returning final response to User.
Hint:
This is mostly about chaining function calls. frontend calls api, which calls backend, which calls db... The return values flow back up the chain.
What You Learned
Today you stepped back from writing algorithms to designing systems.
* Servers are just loops: They wait for input, process it, and repeat.
* Protocols (Request/Response): Standardization allows different systems to talk.
Middleware: The best place to put rules that apply to everyone* (security, logging).* Routing: How we organize massive applications into small, manageable functions.
Why This Matters for AI:When you build an AI application, the AI model is just one small component (the Brain). The Architecture (Server, Database, API) is the Body that allows the Brain to interact with the world. Without this architecture, your AI is just a brain in a jar—smart, but unable to communicate.
Tomorrow:We stop simulating. Tomorrow, you will install FastAPI, a professional Python framework, and build your first real web server that can be accessed by a browser. Get ready to see your code come to life on the web!