Microservices, APIs
& CI/CD
Learn how software talks to software β from REST fundamentals to hands-on FastAPI endpoints and automated deployments.
11
Sections
110
MCQs
CRUD
Examples
Foundation
Prerequisites
Before diving in, make sure you have a few basics in place. Think of these as the tools in your toolbox β you don't need to be an expert, but familiarity helps.
Required knowledge
- Basic computer literacy (files, folders, browsers)
- Elementary programming concepts (variables, functions)
- Comfort using a terminal / command line
Tools to install
- Python 3.10+ β python.org
- pip β comes with Python
- A code editor β VS Code, Cursor, etc.
- Git β for version control & CI/CD
Quick start check: Open a terminal and run
python --version.
If you see a version number, you're ready to proceed.
Core concept
What Is an API?
API stands for Application Programming Interface. In plain English: it's a menu that one program offers to another. Your app doesn't need to know how the kitchen cooks β it just orders from the menu and gets a result back.
When you use a weather app, it doesn't measure temperature itself. It calls a weather service's API: "Give me the forecast for London." The API returns structured data (usually JSON), and the app displays it.
Web standards
REST & HTTP Methods
REST (Representational State Transfer) is a popular style for building web APIs. Resources (users, orders, products) are identified by URLs, and you use standard HTTP methods to act on them β like verbs in a sentence.
| Method | Purpose | Analogy |
|---|---|---|
| GET | Read data | Look at a menu |
| POST | Create new data | Place a new order |
| PUT / PATCH | Update existing data | Change your order |
| DELETE | Remove data | Cancel the order |
Status codes tell you what happened: 200 OK means success,
404 Not Found means the resource doesn't exist,
and 500 Server Error means something broke on the server.
Architecture
Introduction to Microservices
Imagine a restaurant that used to be one giant kitchen where one chef did everything. A monolith app works the same way β one big codebase handles users, payments, inventory, and shipping.
Microservices split that kitchen into specialized stations: a pastry station, a grill, a salad bar. Each service is a small, independent app with its own database, deployed and scaled separately. They talk to each other through APIs.
βοΈ Benefits & trade-offs
- + Teams can deploy services independently
- + Scale only the busy parts (e.g., payment service during sales)
- β More moving parts = more DevOps complexity
- β Network calls between services can fail β you need resilience patterns
DevOps
CI/CD & Automation
CI/CD stands for Continuous Integration and Continuous Delivery/Deployment. Instead of manually copying code to a server every Friday, automation pipelines test and deploy your API whenever you push to Git.
- CI β automatically run tests & lint on every commit
- CD β automatically deploy passing builds to staging or production
Popular tools: GitHub Actions, GitLab CI, Jenkins, CircleCI.
For APIs, pipelines often run unit tests with pytest,
build Docker images, and deploy to cloud platforms.
Hands-on
FastAPI & uvicorn Setup
FastAPI is a modern Python framework for building APIs. It's fast, automatically generates interactive docs, and uses Python type hints for validation. uvicorn is the ASGI server that actually runs your FastAPI app.
Installation
pip install fastapi uvicorn[standard]
Minimal app (main.py)
from fastapi import FastAPI
app = FastAPI(title="My API")
@app.get("/")
def read_root():
return {"message": "Hello, API!"}
Run with uvicorn
uvicorn main:app --reload --host 0.0.0.0 --port 8000
Visit http://127.0.0.1:8000/docs for auto-generated Swagger UI.
Section 07
GET Endpoints β Reading Data
GET requests retrieve data without changing anything. Use path parameters for specific resources and query parameters for filtering.
from fastapi import FastAPI
from typing import List
app = FastAPI()
# In-memory "database" for learning
items_db = [
{"id": 1, "name": "Laptop", "price": 999.99},
{"id": 2, "name": "Mouse", "price": 29.99},
]
@app.get("/items", response_model=List[dict])
def list_items(skip: int = 0, limit: int = 10):
"""GET all items with pagination."""
return items_db[skip : skip + limit]
@app.get("/items/{item_id}")
def get_item(item_id: int):
"""GET a single item by ID."""
for item in items_db:
if item["id"] == item_id:
return item
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="Item not found")
π Try it
curl -i http://localhost:8000/items
curl -i http://localhost:8000/items/1
What the client receives
The client (browser, mobile app, or curl) gets an
HTTP status code plus a JSON body. Use
curl -i to see headers and body together.
GET /items
Success β list all items
[
{ "id": 1, "name": "Laptop", "price": 999.99 },
{ "id": 2, "name": "Mouse", "price": 29.99 }
]
GET /items/1
Success β single item
{
"id": 1,
"name": "Laptop",
"price": 999.99
}
GET /items/999
Item does not exist
{
"detail": "Item not found"
}
Raised by HTTPException(status_code=404, ...) in your route.
GET /items/abc
Bad request β invalid path param
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "item_id"],
"msg": "Input should be a valid integer...",
"input": "abc"
}
]
}
FastAPI auto-validates item_id: int β non-numeric IDs are rejected before your function runs.
Client tip: In JavaScript,
const res = await fetch('/items/1') then
const data = await res.json() gives you the object above.
Check res.ok or res.status before assuming success.
Section 08
POST Endpoints β Creating Data
POST sends a JSON body to create a new resource. FastAPI uses Pydantic models to validate incoming data automatically.
from fastapi import FastAPI, status
from pydantic import BaseModel
app = FastAPI()
items_db = []
next_id = 1
class ItemCreate(BaseModel):
name: str
price: float
@app.post("/items", status_code=status.HTTP_201_CREATED)
def create_item(item: ItemCreate):
global next_id
new_item = {"id": next_id, **item.model_dump()}
items_db.append(new_item)
next_id += 1
return new_item
π Try it
curl -i -X POST http://localhost:8000/items \
-H "Content-Type: application/json" \
-d '{"name": "Keyboard", "price": 79.99}'
What the client receives
On success, the client gets 201 Created and the new record (including the
server-assigned id). On bad input, FastAPI returns
422 with a structured list of validation errors.
POST /items
Success β item created
{
"id": 1,
"name": "Keyboard",
"price": 79.99
}
The client can read id from the response and immediately call GET /items/1.
POST /items
Missing required field
Request body: {"name": "Keyboard"} β no price
{
"detail": [
{
"type": "missing",
"loc": ["body", "price"],
"msg": "Field required",
"input": { "name": "Keyboard" }
}
]
}
POST /items
Wrong data type
Request body: {"name": "Keyboard", "price": "free"}
{
"detail": [
{
"type": "float_parsing",
"loc": ["body", "price"],
"msg": "Input should be a valid number...",
"input": "free"
}
]
}
Pydantic enforces price: float β strings that are not valid numbers are rejected.
DEL
Section 09
PUT/PATCH & DELETE β Edit & Remove
PUT replaces an entire resource; PATCH updates only the fields you send. DELETE removes a resource.
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
items_db = [{"id": 1, "name": "Laptop", "price": 999.99}]
class ItemUpdate(BaseModel):
name: Optional[str] = None
price: Optional[float] = None
def find_item(item_id: int):
for i, item in enumerate(items_db):
if item["id"] == item_id:
return i, item
raise HTTPException(status_code=404, detail="Item not found")
@app.put("/items/{item_id}")
def replace_item(item_id: int, data: ItemUpdate):
"""Full replace β all fields required in practice."""
idx, _ = find_item(item_id)
updated = {"id": item_id, **data.model_dump(exclude_unset=True)}
items_db[idx] = updated
return updated
@app.patch("/items/{item_id}")
def patch_item(item_id: int, data: ItemUpdate):
"""Partial update β only sent fields change."""
idx, item = find_item(item_id)
patch_data = data.model_dump(exclude_unset=True)
items_db[idx] = {**item, **patch_data}
return items_db[idx]
@app.delete("/items/{item_id}", status_code=204)
def delete_item(item_id: int):
idx, _ = find_item(item_id)
items_db.pop(idx)
return None
π Try it
curl -i -X PATCH http://localhost:8000/items/1 -H "Content-Type: application/json" -d '{"price": 899.99}'
curl -i -X DELETE http://localhost:8000/items/1
What the client receives
Updates return the full updated object (200 OK). Deletes often return 204 No Content with an empty body. Missing resources and invalid input produce clear error JSON β same patterns as GET and POST.
PATCH /items/1
Success β partial update
Request body: {"price": 899.99}
{
"id": 1,
"name": "Laptop",
"price": 899.99
}
name unchanged; only price was patched.
DELETE /items/1
Success β item removed
(empty response body)
In JavaScript: res.status === 204 means success with nothing to parse via .json().
PATCH /items/42
Item does not exist
{
"detail": "Item not found"
}
Same JSON shape for DELETE /items/42 when the ID is missing.
PATCH /items/1
Invalid body type
Request body: {"price": "cheap"}
{
"detail": [
{
"type": "float_parsing",
"loc": ["body", "price"],
"msg": "Input should be a valid number...",
"input": "cheap"
}
]
}
Ship it
Production, Testing & CI/CD
A complete microservice isn't just endpoints β it needs tests, containers, and automated deployment.
Example pytest test
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_read_items():
response = client.get("/items")
assert response.status_code == 200
assert isinstance(response.json(), list)
GitHub Actions snippet
# .github/workflows/ci.yml
name: API CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install fastapi uvicorn pytest httpx
- run: pytest
β¨ Key takeaways
- β APIs are contracts β document them (FastAPI does this automatically)
- β Microservices communicate over HTTP/gRPC β design for failure
- β Automate testing and deployment early β CI/CD saves hours later
- β Start simple (monolith or few services), split when you have a clear reason
Toolkit
Understanding curl Commands
curl (pronounced βcurlβ or βsee-urlβ) is a command-line tool that talks to URLs β perfect for testing APIs without building a whole app first. Think of it as speed-dialling a web address: you tell curl where to call, what kind of request to send, and optionally what data to attach.
Every example in the GET, POST, PATCH, and DELETE sections used curl. Here we unpack how those commands are built, flag by flag, in plain language.
βοΈ Does the order matter?
Usually no. curl treats flags (-X,
-H, -d, -i, β¦)
and the URL as separate pieces of the same instruction. You can put the URL
after all flags (Style A) or before trailing flags like -H and
-d (Style B). The request sent over the network is identical.
Same POST β written two ways
Style A (URL last)
curl -i -X POST -H "Content-Type: application/json" -d '{"name":"Keyboard","price":79.99}' http://localhost:8000/items
Style B (URL before -H / -d)
curl -i -X POST http://localhost:8000/items -H "Content-Type: application/json" -d '{"name":"Keyboard","price":79.99}'
Why both appear in this tutorial: multi-line examples often put the URL right after
-X POST so the next lines clearly attach headers and body to that address.
Single-line examples sometimes group all flags first. Pick whichever reads clearer to you.
Essential flags & options
Flags start with a hyphen (-) or double hyphen (--). They modify how curl behaves.
| Flag | Plain-English meaning |
|---|---|
-X METHOD | Pick the HTTP verb (GET, POST, PATCH, DELETEβ¦) |
-H "Name: value" | Add a header β metadata about the request |
-d '...' | Attach a body (usually JSON) to the request |
-i | Include response headers in the output |
-v | Verbose β show full request/response conversation |
-s | Silent β hide progress meter |
-u user:pass | Basic authentication credentials |
--fail | Treat HTTP errors (4xx/5xx) as command failure |
Worked examples (with explanations)
Example 1 β Simple GET (read data)
curl http://localhost:8000/items
- curl β runs the tool.
- http://localhost:8000/items β the address.
localhostmeans βthis computerβ; port8000is where uvicorn listens;/itemsis the API path. - No
-Xneeded β curl assumes GET when you only pass a URL (like opening a page in a browser). - Youβll see JSON printed in the terminal β thatβs the response body from FastAPI.
Example 2 β GET with response headers (-i)
curl -i http://localhost:8000/items/1
- -i (include) β shows the βenvelopeβ (status line + headers) and the message inside (JSON body).
- Look for the first line:
HTTP/1.1 200 OKβ thatβs how the client knows the request succeeded before reading JSON. - Use this whenever you need to distinguish
200vs404vs422at a glance.
Example 3 β POST (create data)
curl -i -X POST http://localhost:8000/items \
-H "Content-Type: application/json" \
-d '{"name": "Keyboard", "price": 79.99}'
- -X POST β explicitly says βcreate somethingβ (donβt just read).
- http://localhost:8000/items β the URL appears before
-Hand-dhere (Style B). That is valid β you could also move the URL to the very end (Style A) and get the same result. - -H "Content-Type: application/json" β tells the server βthe body is JSON text.β Without this, FastAPI may not parse the body correctly.
- -d '{...}' β the data payload (d = data). The single quotes wrap JSON so the shell doesnβt break on inner double quotes.
- The backslash
\at line ends continues the command on the next line β purely for readability; it does not change flag order rules. - Expect
201 Createdand JSON with the newid.
Example 4 β PATCH (partial update)
curl -i -X PATCH http://localhost:8000/items/1 \
-H "Content-Type: application/json" \
-d '{"price": 899.99}'
- -X PATCH β βchange only what I send.β Here we update
priceonly;namestays the same. - /items/1 β the
1in the URL identifies which record to patch (path parameter). - Again,
-Hand-dcome after the URL in this layout β equivalent to putting them before the URL.
Example 5 β DELETE (remove data)
curl -i -X DELETE http://localhost:8000/items/1
- -X DELETE β asks the server to remove the resource at that URL.
- No
-dbody needed β youβre not sending new data, just saying βdelete this ID.β - Often returns
204 No Contentβ success with an empty body. Thatβs normal; check the status line, not the body.
Example 6 β Query parameters (filters in the URL)
curl "http://localhost:8000/items?skip=0&limit=5"
- Everything after
?is the query string β optional filters askey=valuepairs separated by&. - Quotes wrap the URL so the shell doesnβt treat
&as βrun in background.β - Maps to FastAPI parameters:
skip: int = 0, limit: int = 10in your route function.
π‘ Quick cheat sheet
curl URLcurl -X POST -H ... -d ... URLcurl -X PATCH -H ... -d ... URL/idcurl -X DELETE URL/idTip: add -i to any command when learning. For POST/PATCH, the URL can sit before or after -H / -d.