6.3: FastAPI Backend Setup

Understand domain names and DNS to connect custom domains to your deployed websites. Learn how DNS works, how to register domains, and configure DNS records to point your domain to your hosting platform.

1. Introduction to Backend Development

What is a Backend API?

A backend API (Application Programming Interface) is server-side code that:

  • Processes data and business logic
  • Manages databases
  • Handles authentication and authorization
  • Serves data to frontend applications

Frontend + Backend Architecture:

Vue.js Frontend (Browser)
         ↓ HTTP requests
FastAPI Backend (Server)
         ↓
Database (PostgreSQL, MongoDB, etc.)

Why FastAPI?

FastAPI is a modern Python web framework that offers:

  • Fast: High performance (comparable to Node.js)
  • Easy: Simple syntax, built on Python
  • Type safety: Uses Python type hints
  • Automatic docs: Interactive API documentation
  • Async support: Handle many requests efficiently

Perfect for Python developers learning web development!

Comparison with other frameworks:

FrameworkLanguageSpeedLearning Curve
FastAPIPythonFastEasy
DjangoPythonMediumModerate
ExpressNode.jsFastEasy
FlaskPythonSlowEasy

2. Setting Up FastAPI

Prerequisites

  • Python 3.8+ installed
  • Basic Python knowledge (you have this!)
  • pip (Python package manager)

Check Python version:

python --version
# or
python3 --version

Create a Project

Step 1: Create project directory

mkdir my-api
cd my-api

Step 2: Create virtual environment

# Create virtual environment
python -m venv venv

# Activate it
# On macOS/Linux:
source venv/bin/activate

# On Windows:
venv\Scripts\activate

You should see (venv) in your terminal prompt.

Step 3: Install FastAPI and Uvicorn

pip install fastapi uvicorn[standard]

Explanation:

  • fastapi: The web framework
  • uvicorn: ASGI server to run FastAPI

Step 4: Create requirements.txt

pip freeze > requirements.txt

This file lists all dependencies for deployment.


3. Your First FastAPI Application

Create main.py

# main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello, FastAPI!"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "query": q}

Explanation:

  • app = FastAPI(): Creates FastAPI application
  • @app.get("/"): Defines GET endpoint at root
  • Type hints: item_id: int provides automatic validation
  • q: str = None: Optional query parameter

Run the Server

uvicorn main:app --reload

Explanation:

  • main: Filename (main.py)
  • app: FastAPI instance variable
  • --reload: Auto-restart on code changes (development only)

Output:

INFO:     Uvicorn running on http://127.0.0.1:8000
INFO:     Application startup complete.

Test Your API

Open browser and visit:

  1. Root endpoint: http://127.0.0.1:8000
    • Returns: {"message": "Hello, FastAPI!"}
  2. Item endpoint: http://127.0.0.1:8000/items/42?q=test
    • Returns: {"item_id": 42, "query": "test"}
  3. Interactive docs: http://127.0.0.1:8000/docs
    • Automatic API documentation (Swagger UI)
    • Test endpoints directly in browser!
  4. Alternative docs: http://127.0.0.1:8000/redoc
    • ReDoc documentation

4. Building a RESTful API

REST Principles

REST (Representational State Transfer) uses HTTP methods:

MethodPurposeExample
GETRetrieve dataGet user list
POSTCreate dataCreate new user
PUTUpdate/replaceUpdate user
PATCHPartial updateUpdate user email
DELETEDelete dataDelete user

Example: Todo API

Create a simple todo API:

# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional

app = FastAPI(title="Todo API", version="1.0.0")

# Data model
class Todo(BaseModel):
    id: Optional[int] = None
    title: str
    description: Optional[str] = None
    completed: bool = False

# In-memory database (for learning)
todos: List[Todo] = []
todo_id_counter = 1

# Endpoints

@app.get("/")
def root():
    return {"message": "Todo API is running"}

@app.get("/todos", response_model=List[Todo])
def get_todos():
    """Get all todos"""
    return todos

@app.get("/todos/{todo_id}", response_model=Todo)
def get_todo(todo_id: int):
    """Get a specific todo"""
    for todo in todos:
        if todo.id == todo_id:
            return todo
    raise HTTPException(status_code=404, detail="Todo not found")

@app.post("/todos", response_model=Todo)
def create_todo(todo: Todo):
    """Create a new todo"""
    global todo_id_counter
    todo.id = todo_id_counter
    todo_id_counter += 1
    todos.append(todo)
    return todo

@app.put("/todos/{todo_id}", response_model=Todo)
def update_todo(todo_id: int, updated_todo: Todo):
    """Update a todo"""
    for i, todo in enumerate(todos):
        if todo.id == todo_id:
            updated_todo.id = todo_id
            todos[i] = updated_todo
            return updated_todo
    raise HTTPException(status_code=404, detail="Todo not found")

@app.delete("/todos/{todo_id}")
def delete_todo(todo_id: int):
    """Delete a todo"""
    for i, todo in enumerate(todos):
        if todo.id == todo_id:
            todos.pop(i)
            return {"message": "Todo deleted"}
    raise HTTPException(status_code=404, detail="Todo not found")

Key concepts:

1. Pydantic Models:

class Todo(BaseModel):
    id: Optional[int] = None
    title: str
    completed: bool = False
  • Automatic validation
  • Type checking
  • JSON serialization

2. Path Parameters:

@app.get("/todos/{todo_id}")
def get_todo(todo_id: int):

3. Request Body:

@app.post("/todos")
def create_todo(todo: Todo):

4. Response Models:

@app.get("/todos", response_model=List[Todo])

5. Error Handling:

raise HTTPException(status_code=404, detail="Todo not found")

Test the API

Using the interactive docs (http://127.0.0.1:8000/docs):

  1. Try POST /todos:
{
  "title": "Learn FastAPI",
  "description": "Complete the FastAPI tutorial",
  "completed": false
}
  1. Try GET /todos - see your created todo
  2. Try PUT /todos/1 to update
  3. Try DELETE /todos/1 to delete

5. Handling CORS

What is CORS?

CORS (Cross-Origin Resource Sharing) is a security feature that restricts:

Frontend (http://localhost:3000)
    ↓ Request
Backend (http://localhost:8000)
    ↓ Blocked by browser!

Why? Browsers block requests from different origins (domain/port) for security.

Enable CORS in FastAPI

# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# Configure CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:3000",  # Vue dev server
        "http://localhost:5173",  # Vite dev server
        "https://myapp.netlify.app",  # Production frontend
    ],
    allow_credentials=True,
    allow_methods=["*"],  # Allow all methods
    allow_headers=["*"],  # Allow all headers
)

# Your routes...

For development (less secure):

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # Allow all origins
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

Security note: In production, specify exact origins!


6. Connecting Vue.js to FastAPI

Vue.js Setup

Install Axios (HTTP client):

npm install axios

Create API service (src/services/api.js):

// src/services/api.js
import axios from 'axios'

const apiClient = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8000',
  headers: {
    'Content-Type': 'application/json'
  }
})

export default {
  // Get all todos
  getTodos() {
    return apiClient.get('/todos')
  },

  // Get single todo
  getTodo(id) {
    return apiClient.get(`/todos/${id}`)
  },

  // Create todo
  createTodo(todo) {
    return apiClient.post('/todos', todo)
  },

  // Update todo
  updateTodo(id, todo) {
    return apiClient.put(`/todos/${id}`, todo)
  },

  // Delete todo
  deleteTodo(id) {
    return apiClient.delete(`/todos/${id}`)
  }
}

Vue Component Example

<!-- src/components/TodoList.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import api from '@/services/api'

const todos = ref([])
const newTodo = ref('')
const loading = ref(false)
const error = ref(null)

async function fetchTodos() {
  loading.value = true
  try {
    const response = await api.getTodos()
    todos.value = response.data
  } catch (err) {
    error.value = 'Failed to fetch todos'
    console.error(err)
  } finally {
    loading.value = false
  }
}

async function addTodo() {
  if (!newTodo.value.trim()) return

  try {
    const response = await api.createTodo({
      title: newTodo.value,
      completed: false
    })
    todos.value.push(response.data)
    newTodo.value = ''
  } catch (err) {
    error.value = 'Failed to create todo'
    console.error(err)
  }
}

async function deleteTodo(id) {
  try {
    await api.deleteTodo(id)
    todos.value = todos.value.filter(t => t.id !== id)
  } catch (err) {
    error.value = 'Failed to delete todo'
    console.error(err)
  }
}

onMounted(() => {
  fetchTodos()
})
</script>

<template>
  <div class="todo-list">
    <h1>Todo List</h1>

    <div v-if="error" class="error">{{ error }}</div>

    <div class="add-todo">
      <input
        v-model="newTodo"
        @keyup.enter="addTodo"
        placeholder="Add a new todo"
      >
      <button @click="addTodo">Add</button>
    </div>

    <div v-if="loading">Loading...</div>

    <ul v-else>
      <li v-for="todo in todos" :key="todo.id">
        <span>{{ todo.title }}</span>
        <button @click="deleteTodo(todo.id)">Delete</button>
      </li>
    </ul>
  </div>
</template>

<style scoped>
.todo-list {
  max-width: 600px;
  margin: 2rem auto;
}

.error {
  background: #ffebee;
  color: #c62828;
  padding: 1rem;
  border-radius: 4px;
  margin-bottom: 1rem;
}

.add-todo {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

input {
  flex: 1;
  padding: 0.5rem;
  border: 1px solid #ddd;
  border-radius: 4px;
}

button {
  padding: 0.5rem 1rem;
  background: #42b883;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background: #35a372;
}

li {
  display: flex;
  justify-content: space-between;
  padding: 0.75rem;
  border-bottom: 1px solid #eee;
}
</style>

Environment Variables

Create .env file:

# .env
VITE_API_URL=http://localhost:8000

Production .env:

# .env.production
VITE_API_URL=https://my-api.railway.app

7. Deploying FastAPI

Deployment Options

Free hosting platforms:

  1. Railway - Easiest, generous free tier
  2. Render - Simple, free tier available
  3. Fly.io - Good free tier
  4. PythonAnywhere - Free tier for simple APIs

Deploy to Railway

Step 1: Prepare for deployment

Create Procfile:

# Procfile
web: uvicorn main:app --host 0.0.0.0 --port $PORT

Update requirements.txt:

pip freeze > requirements.txt

Create runtime.txt (specify Python version):

python-3.11.0

Step 2: Push to GitHub

git init
git add .
git commit -m "Initial FastAPI app"
git remote add origin https://github.com/yourusername/my-api.git
git push -u origin main

Step 3: Deploy on Railway

  1. Go to https://railway.app
  2. Sign up with GitHub
  3. Click "New Project" → "Deploy from GitHub repo"
  4. Select your repository
  5. Railway auto-detects Python and deploys!

Step 4: Get your URL

Railway provides a URL like: https://my-api.railway.app

Step 5: Test deployment

Visit: https://my-api.railway.app/docs

Deploy to Render

Step 1: Create render.yaml

# render.yaml
services:
  - type: web
    name: my-api
    env: python
    buildCommand: pip install -r requirements.txt
    startCommand: uvicorn main:app --host 0.0.0.0 --port $PORT

Step 2: Push to GitHub

Step 3: Connect to Render

  1. Go to https://render.com
  2. Sign up with GitHub
  3. New → Web Service
  4. Connect your repository
  5. Render auto-deploys!

8. Database Integration

SQLite (Simple)

Install SQLAlchemy:

pip install sqlalchemy

Database setup:

# database.py
from sqlalchemy import create_engine, Column, Integer, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "sqlite:///./todos.db"

engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

# Todo model
class TodoDB(Base):
    __tablename__ = "todos"

    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, index=True)
    description = Column(String, nullable=True)
    completed = Column(Boolean, default=False)

# Create tables
Base.metadata.create_all(bind=engine)

Update main.py:

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from database import SessionLocal, TodoDB
from pydantic import BaseModel

app = FastAPI()

# Pydantic model
class Todo(BaseModel):
    title: str
    description: str = None
    completed: bool = False

    class Config:
        orm_mode = True

# Dependency
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.post("/todos")
def create_todo(todo: Todo, db: Session = Depends(get_db)):
    db_todo = TodoDB(**todo.dict())
    db.add(db_todo)
    db.commit()
    db.refresh(db_todo)
    return db_todo

@app.get("/todos")
def get_todos(db: Session = Depends(get_db)):
    return db.query(TodoDB).all()

PostgreSQL (Production)

For production, use PostgreSQL:

pip install psycopg2-binary

Database URL:

SQLALCHEMY_DATABASE_URL = "postgresql://user:password@host:5432/dbname"

Or use environment variable:

import os
SQLALCHEMY_DATABASE_URL = os.getenv("DATABASE_URL")

Railway provides free PostgreSQL:

  1. Add PostgreSQL plugin in Railway
  2. Copy DATABASE_URL
  3. Use in your app

9. Authentication Basics

Simple API Key Authentication

# main.py
from fastapi import FastAPI, Header, HTTPException

app = FastAPI()

API_KEY = "your-secret-api-key"

async def verify_api_key(x_api_key: str = Header()):
    if x_api_key != API_KEY:
        raise HTTPException(status_code=401, detail="Invalid API Key")
    return x_api_key

@app.get("/protected")
async def protected_route(api_key: str = Depends(verify_api_key)):
    return {"message": "You have access!"}

Usage from frontend:

axios.get('/protected', {
  headers: {
    'X-API-Key': 'your-secret-api-key'
  }
})

JWT Authentication (Advanced)

Install dependencies:

pip install python-jose[cryptography] passlib[bcrypt] python-multipart

Basic JWT setup:

from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext

SECRET_KEY = "your-secret-key-change-in-production"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

@app.post("/login")
def login(username: str, password: str):
    # Verify credentials (simplified)
    if username == "admin" and password == "password":
        access_token = create_access_token(data={"sub": username})
        return {"access_token": access_token, "token_type": "bearer"}
    raise HTTPException(status_code=401, detail="Invalid credentials")

10. Knowledge Check

Question 1: What is the purpose of CORS?

Show answer CORS (Cross-Origin Resource Sharing) controls which domains can make requests to your API. It's a security feature to prevent unauthorized websites from accessing your backend.

Question 2: Why use Pydantic models in FastAPI?

Show answer Pydantic models provide automatic data validation, type checking, and JSON serialization/deserialization, making APIs more robust and reducing errors.

Question 3: What's the difference between GET and POST?

Show answer GET retrieves data from the server, POST sends data to create new resources. GET is idempotent (safe to repeat), POST is not.

Question 4: How do you connect a Vue app to FastAPI?

Show answer Use Axios to make HTTP requests to FastAPI endpoints, configure CORS on the backend, and use environment variables to store the API URL.

Question 5: Where should you store sensitive API keys?

Show answer In environment variables, never commit them to Git. Use .env files locally and platform environment variables in production.

11. Practical Exercises

Exercise 6.3.1: Build a Simple API

  1. Create a FastAPI app with GET and POST endpoints
  2. Implement a simple in-memory data store
  3. Test with the interactive docs
  4. Deploy to Railway

Exercise 6.3.2: Connect Vue to FastAPI

  1. Create a Vue.js frontend
  2. Set up Axios API service
  3. Connect to your FastAPI backend
  4. Implement full CRUD operations

Exercise 6.3.3: Add Database

  1. Integrate SQLite with SQLAlchemy
  2. Create database models
  3. Update endpoints to use database
  4. Test persistence

Exercise 6.3.4: Deploy Full Stack

  1. Deploy FastAPI to Railway
  2. Deploy Vue to Vercel/Netlify
  3. Configure CORS for production
  4. Test the full application

12. Key Takeaways

  • FastAPI is a modern, fast Python framework perfect for APIs
  • RESTful APIs use HTTP methods (GET, POST, PUT, DELETE) consistently
  • Pydantic models provide automatic validation and type safety
  • CORS must be configured to allow frontend-backend communication
  • Deployment is easy with Railway, Render, or other platforms
  • Environment variables keep configuration flexible and secure
  • Database integration enables data persistence
  • Authentication protects sensitive endpoints
  • Interactive docs at /docs make testing easy
  • Type hints enable automatic validation and better developer experience

13. Next Steps

Excellent work! You now know how to build and deploy backend APIs with FastAPI.

In Lesson 6.4: Full-Stack Deployment & Production Best Practices, you'll learn how to deploy complete full-stack applications, optimize performance, implement monitoring, and follow production best practices.

Ready to master production deployment? Let's finish strong!


Further Resources

Official Documentation:

Tutorials:

Deployment Platforms:

Database Tutorials: