Skip to content
OVEX TECH
Education & E-Learning

Build Your First Python API with FastAPI

Build Your First Python API with FastAPI

Master the Fundamentals of Building Modern Python APIs with FastAPI

This guide will walk you through the essential concepts and practical steps to build a high-performance API using FastAPI, a modern Python web framework. You’ll learn about the overall architecture, creating routes, defining data models with Pydantic, and leveraging automatic interactive documentation.

What You’ll Learn

  • Understanding FastAPI’s core architecture and benefits.
  • Setting up your development environment with virtual environments.
  • Creating basic API routes using decorators.
  • Implementing path parameters and query parameters.
  • Utilizing Pydantic for data validation and serialization.
  • Organizing your API with routers.
  • Implementing simple data storage using JSON files.
  • Interacting with your API through interactive Swagger documentation.

Prerequisites

  • Basic Python programming knowledge.
  • Familiarity with the command line/terminal.
  • A code editor or IDE (like VS Code).

Step 1: Project Setup and Virtual Environment

First, create a new folder for your project. You can name it something like fastapi-issue-tracker. Open this folder in your code editor and your terminal.

Next, create a virtual environment to manage project dependencies. In your terminal, run:

python -m venv venv

This command creates a venv folder. Now, activate the virtual environment:

On macOS/Linux:

source venv/bin/activate

On Windows:

venvScriptsactivate

You’ll see the virtual environment name ((venv)) at the beginning of your terminal prompt, indicating it’s active.

Step 2: Install FastAPI and Uvicorn

With your virtual environment activated, install FastAPI and Uvicorn (an ASGI server) using pip:

pip install "fastapi[all]" uvicorn

This command installs FastAPI along with common extras like Pydantic and Swagger UI, and Uvicorn to run your application.

Step 3: Create the Main Application File

Create a file named main.py in the root of your project. This will be your application’s entry point.

Import the FastAPI class and initialize your application:

from fastapi import FastAPI

app = FastAPI()

Step 4: Create a Health Check Endpoint

Let’s add a simple endpoint to verify the server is running. Add the following code to main.py:

from fastapi import FastAPI

app = FastAPI()

@app.get("/health")
def health_check():
    return {"status": "ok"}

The @app.get("/health") decorator registers the health_check function to handle GET requests to the /health path. The function returns a simple JSON response.

Step 5: Run the Development Server

You can run your FastAPI application using Uvicorn. In your terminal, execute:

uvicorn main:app --reload

main refers to the main.py file, and app refers to the FastAPI() instance you created. The --reload flag automatically restarts the server when you make code changes.

Your server will now be running, typically at http://127.0.0.1:8000.

Step 6: Explore Interactive Documentation

FastAPI automatically generates interactive API documentation. Visit http://127.0.0.1:8000/docs in your browser to see the Swagger UI. You can view your available endpoints and even test them directly from this interface.

You should see the /health endpoint. Click on it, then click “Try it out” and “Execute” to send a request and see the {"status": "ok"} response.

Step 7: Implementing Basic Item Routes (Optional Example)

Before structuring for a larger application, let’s add a few more basic routes to demonstrate different HTTP methods and parameters.

Add the following to main.py:


items = [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]

@app.get("/items/")
def get_items():
    return items

@app.get("/items/{item_id}")
def get_item(item_id: int):
    for item in items:
        if item["id"] == item_id:
            return item
    return {"message": "Item not found"} # Consider using HTTPException for better error handling

@app.post("/items/")
def create_item(name: str):
    new_item = {"id": len(items) + 1, "name": name}
    items.append(new_item)
    return new_item

Explanation:

  • GET /items/: Returns a list of all items.
  • GET /items/{item_id}: Retrieves a specific item by its ID. The {item_id} in the path is a path parameter, and the item_id: int in the function signature tells FastAPI to expect an integer and perform type validation.
  • POST /items/: Creates a new item. The name: str parameter is automatically interpreted as a request body parameter (since it’s not a path parameter) and expects a JSON body with a “name” field.

Refresh your /docs page to see these new endpoints and test them.

Step 8: Structuring with API Routers

For larger applications, it’s best to organize your routes into separate files using API Routers. This keeps your main.py clean.

  1. Create a folder named app in your project root.
  2. Inside app, create another folder named routes.
  3. Inside routes, create a file named issues.py.

Add the following to app/routes/issues.py:


from fastapi import APIRouter

router = APIRouter(
    prefix="/api/v1/issues",
    tags=["Issues"]
)

@router.get("/")
def get_issues():
    return [] # Placeholder for now

# Add more issue-related routes here later

Explanation:

  • APIRouter is imported and initialized.
  • prefix="/api/v1/issues" sets a base path for all routes defined in this router.
  • tags=["Issues"] helps organize endpoints in the Swagger UI.
  • Routes are now defined using @router.get, @router.post, etc., instead of @app.get.

Now, you need to include this router in your main application. Modify main.py:


from fastapi import FastAPI
from app.routes.issues import router as issues_router # Import the router

app = FastAPI()

app.include_router(issues_router) # Include the router in the main app

# Keep the health check or remove it as desired
@app.get("/health")
def health_check():
    return {"status": "ok"}

# Remove the old item routes if you added them

Restart your Uvicorn server (Ctrl+C and then run uvicorn main:app --reload again). Refresh /docs. You should now see the /api/v1/issues/ endpoint under the “Issues” tag.

Step 9: Data Modeling with Pydantic

FastAPI uses Pydantic for data validation and serialization. This ensures that the data sent to and received from your API conforms to a defined structure.

  1. Create a file named schemas.py inside the app folder.

Add the following to app/schemas.py:


from pydantic import BaseModel, Field
from typing import Optional

class IssueBase(BaseModel):
    title: str = Field(..., min_length=3, max_length=100)
    description: Optional[str] = Field(None, min_length=5, max_length=1000)

class IssueCreate(IssueBase):
    pass # Inherits title and description from IssueBase

class Issue(IssueBase):
    id: int
    status: str # Example: 'open', 'in_progress', 'closed'

    class Config:
        orm_mode = True # Allows mapping to ORM models, useful for databases

Explanation:

  • BaseModel is the base class for Pydantic models.
  • Field allows you to add validation rules like minimum/maximum length.
  • Optional[str] indicates that the description is not required.
  • IssueCreate is used for data sent when creating an issue.
  • Issue is used for data returned by the API, including an id and status.

Step 10: Implementing Issue Storage

We’ll use a simple JSON file for data storage. This is suitable for learning but should be replaced with a proper database in production.

  1. Create a file named storage.py inside the app folder.

Add the following to app/storage.py:


import json
from pathlib import Path

DATA_DIR = Path("data")
ISSUES_FILE = DATA_DIR / "issues.json"

def load_issues():
    if not ISSUES_FILE.exists():
        return []
    try:
        with open(ISSUES_FILE, "r") as f:
            content = f.read()
            if not content.strip():
                return []
            return json.loads(content)
    except json.JSONDecodeError:
        return [] # Handle empty or invalid JSON file

def save_issues(issues_data):
    DATA_DIR.mkdir(parents=True, exist_ok=True)
    with open(ISSUES_FILE, "w") as f:
        json.dump(issues_data, f, indent=2)

Explanation:

  • DATA_DIR and ISSUES_FILE define the location for our data.
  • load_issues reads data from issues.json, handling cases where the file or directory doesn’t exist, or the file is empty/corrupt.
  • save_issues ensures the data directory exists and writes the provided list of issues to issues.json with pretty printing (indent=2).

Step 11: Updating Issue Routes with Pydantic and Storage

Now, let’s integrate Pydantic models and our storage logic into the issue routes.

Modify app/routes/issues.py:


from fastapi import APIRouter, HTTPException, status
from app.schemas import IssueCreate, Issue
from app.storage import load_issues, save_issues
from typing import List

router = APIRouter(
    prefix="/api/v1/issues",
    tags=["Issues"]
)

@router.get("/", response_model=List[Issue])
def get_issues():
    return load_issues()

@router.post("/", response_model=Issue, status_code=status.HTTP_201_CREATED)
def create_issue(issue: IssueCreate):
    issues = load_issues()
    new_id = 1
    if issues:
        new_id = max(issue['id'] for issue in issues) + 1

    new_issue = Issue(
        id=new_id,
        title=issue.title,
        description=issue.description,
        status="open" # Default status
    )
    issues.append(new_issue.dict())
    save_issues(issues)
    return new_issue

@router.get("/{issue_id}", response_model=Issue)
def get_issue(issue_id: int):
    issues = load_issues()
    for issue in issues:
        if issue["id"] == issue_id:
            return Issue(**issue)
    raise HTTPException(status_code=404, detail="Issue not found")

# You would add PUT/PATCH and DELETE routes here as well

Explanation:

  • We import necessary components: APIRouter, HTTPException, Pydantic models, and storage functions.
  • response_model=List[Issue] and response_model=Issue tell FastAPI what the structure of the response should be, which is also reflected in the Swagger UI.
  • The create_issue endpoint now accepts an IssueCreate model, automatically validating the input data. It assigns a new ID, sets the default status to “open”, saves the data, and returns the created issue as an Issue model.
  • The get_issue endpoint retrieves a single issue by ID, returning a 404 error if not found.

Tip: For updating issues (PUT/PATCH), you would create an IssueUpdate Pydantic model (likely inheriting from IssueBase but making all fields optional) and implement similar logic for loading, modifying, and saving.

Conclusion

You have now successfully set up a FastAPI project, created basic routes, implemented data validation using Pydantic, organized your code with routers, and added simple JSON file storage. This foundation allows you to build more complex and powerful APIs. Remember to explore the interactive documentation at /docs to test your endpoints thoroughly.


Source: FastAPI Crash Course – Modern Python API Development (YouTube)

Leave a Reply

Your email address will not be published. Required fields are marked *

Written by

John Digweed

1,249 articles

Life-long learner.