FastAPI Interview Questions 16-20 (Dependency Injection, Background Tasks, & Project Structure)
Welcome back! You have mastered the basics of validation and models. Now, we move into the territory of professional software engineering. Writing a "Hello World" app is easy, but how do you write an app that has 50 endpoints, connects to a database, and sends emails without freezing the server?
In this lesson, we cover FastAPI's superpower: Dependency Injection. We will also look at how to run tasks in the background and how to structure your code so it doesn't turn into one giant, unreadable file.
16. What are dependency injections (Depends) in FastAPI?
Dependency Injection (DI) is a design pattern where a function "declares" what it needs (dependencies) to work, and the framework provides those dependencies automatically. In FastAPI, this is handled using the Depends class.
Analogy: The Chef and the Prep Cook
Imagine a Chef (your path operation function) who needs chopped onions. The Chef doesn't stop cooking to chop onions. They simply ask for them. A Prep Cook (the dependency) chops the onions and hands them over. The Chef doesn't care how they were chopped, just that they are ready.
Why use it?
- Code Reuse: Write logic (like verifying a token) once, reuse it everywhere.
- Testing: You can easily swap a real database connection for a fake one during tests.
- Cleaner Code: Your path operations focus on logic, not setup.
from fastapi import FastAPI, Depends
app = FastAPI()
# 1. Define the dependency
# This function runs BEFORE the path operation
def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
# 2. Inject the dependency
@app.get("/items/")
def read_items(commons: dict = Depends(common_parameters)):
# 'commons' contains the result of the function above
return commons
@app.get("/users/")
def read_users(commons: dict = Depends(common_parameters)):
# We reused the logic!
return commons
17. How do you use Dependency Injection for DB sessions?
This is the most common use case for Depends. You need a database session for a request, and you mustclose it after the request is finished, even if an error occurs.
FastAPI solves this elegantly using a generator function with the yield keyword.
- Code before yield: Runs before the request starts (connect to DB).
- yield: Pauses execution and hands the session to the route.
- Code after yield: Runs after the response is sent (close DB).
# database.py (mock setup)
from sqlalchemy.orm import Session
# This is our dependency function
def get_db():
db = SessionLocal() # Create session
try:
yield db # Send session to the route
finally:
db.close() # Clean up afterwards
# main.py
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
app = FastAPI()
@app.post("/users/")
def create_user(db: Session = Depends(get_db)):
# Use the 'db' session here
# No need to worry about closing it!
return {"message": "User created"}
18. Explain FastAPI background tasks.
Sometimes you need to perform a slow action (like sending an email or processing a file) without making the user wait. You want to return a "Success" response immediately and continue working in the background.
FastAPI provides BackgroundTasks for this purpose. It is lighter than using a full task queue like Celery, perfect for small jobs.
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
# A simple function to simulate work
def write_log(message: str):
with open("log.txt", "a") as log:
log.write(message + "
")
@app.post("/send-notification/{email}")
def send_notification(email: str, background_tasks: BackgroundTasks):
# 1. Add the task to the queue
background_tasks.add_task(write_log, f"Notification sent to {email}")
# 2. Return response immediately
# The 'write_log' function runs AFTER this return
return {"message": "Notification sent in the background"}
Interview Tip: Be clear that for heavy, CPU-intensive tasks (like video processing), you should still use Celery or RQ. FastAPI background tasks run in the same event loop as your application.
19. What are FastAPI routers and why are they used?
As your application grows, putting all endpoints into a single main.pyfile becomes unmanageable. APIRouter allows you to split your application into multiple smaller files, each handling a specific domain (like users, items, or login).
Analogy: Departments in a Companymain.py is the CEO. It shouldn't micromanage every task. Instead, it delegates to departments (Routers). There is a "Users Department" and an "Items Department". The CEO just connects them all together.
# users.py
from fastapi import APIRouter
router = APIRouter()
@router.get("/users/", tags=["users"])
def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
# main.py
from fastapi import FastAPI
from .users import router as users_router
app = FastAPI()
# Connect the router to the main app
app.include_router(users_router)
20. How do you organize large FastAPI projects using APIRouter?
This is a system design question regarding code structure. A standard production-grade FastAPI structure separates concerns into Models (DB), Schemas (Pydantic), Routers (Endpoints), and CRUD (Logic).
Here is the recommended directory structure:
app/
├── main.py # The entry point (initializes FastAPI)
├── dependencies.py # reusable dependencies (get_db, get_current_user)
├── routers/ # Folder for API endpoints
│ ├── __init__.py
│ ├── users.py # router for /users
│ └── items.py # router for /items
├── internal/ # Internal admin logic
│ └── admin.py
├── models.py # SQLAlchemy database models
├── schemas.py # Pydantic models (request/response)
└── crud.py # Functions that interact with the DB
In your main.py, you would gather everything like this:
# app/main.py
from fastapi import FastAPI
from .routers import users, items
from .internal import admin
app = FastAPI()
app.include_router(users.router)
app.include_router(items.router)
app.include_router(
admin.router,
prefix="/admin",
tags=["admin"],
responses={418: {"description": "I'm a teapot"}},
)