Goals of a Clean Setup
A good FastAPI setup makes local development fast (reload, clear entry point), keeps code easy to navigate (predictable folders), and supports growth (routers separated from business logic, tests from day one). In this chapter you will create a minimal but scalable project layout and run it with an ASGI server.
Create the Project and Virtual Environment
1) Create a folder and initialize a virtual environment
Create a new directory and a virtual environment inside it. The commands vary slightly by OS.
# create project folder
mkdir fastapi-project
cd fastapi-project
# macOS/Linux
python -m venv .venv
source .venv/bin/activate
# Windows (PowerShell)
py -m venv .venv
.\.venv\Scripts\Activate.ps1Keep the virtual environment inside the project (commonly named .venv) so your editor can detect it easily. Add it to .gitignore so it is never committed.
2) Install core dependencies
Install FastAPI and an ASGI server for local development. You will use uvicorn with auto-reload so code changes restart the server automatically.
python -m pip install --upgrade pip
pip install fastapi uvicorn[standard]uvicorn[standard] includes useful extras (like better reloading and performance dependencies) that are convenient in development.
Continue in our app.
You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.
Or continue reading below...Download the app
3) Capture dependencies
For a simple beginner-friendly workflow, you can pin dependencies into requirements.txt.
pip freeze > requirements.txtWhenever you add a new package, re-run the freeze command to keep the file updated.
Organize the Folder Structure
Start with a structure that is small today but won’t collapse when you add features. A common approach is to keep the FastAPI application code under app/ and split responsibilities by layer.
fastapi-project/
app/
__init__.py
main.py
routers/
__init__.py
health.py
models/
__init__.py
schemas/
__init__.py
services/
__init__.py
tests/
__init__.py
.gitignore
requirements.txtWhat each folder is for
app/main.py: application entry point (createsFastAPI(), includes routers).app/routers/: API layer (path operations, request/response handling, status codes). Routers should be thin.app/schemas/: Pydantic models that define request and response shapes (DTOs). These are your API contracts.app/models/: domain or persistence models (for example ORM models). Keep them separate from API schemas.app/services/: business logic (use cases). Services should not depend on FastAPI objects likeRequestorResponse.tests/: automated tests. Keeping this from the start encourages a stable workflow.
Minimal Application Entry Point
Create app/main.py as the single place where the FastAPI app is instantiated and routers are registered. This keeps startup predictable and avoids circular imports as the project grows.
from fastapi import FastAPI
from app.routers.health import router as health_router
app = FastAPI(title="FastAPI Project")
app.include_router(health_router)Notice that main.py does not contain endpoint logic. It only wires the application together.
Define a Router (API Layer)
Routers group endpoints by feature and can be mounted with prefixes and tags. Create app/routers/health.py:
from fastapi import APIRouter
router = APIRouter(tags=["health"])
@router.get("/health")
def health_check() -> dict:
return {"status": "ok"}This is intentionally small: it proves the project boots, routing works, and OpenAPI documentation is generated.
Structuring for Growth: Routers vs Services
As soon as endpoints do more than return static data, separate concerns:
- Router responsibilities: parse inputs, call a service, return outputs (and HTTP status codes).
- Service responsibilities: implement business rules and orchestration. Services should be testable without running the web server.
Even for a health check, you can see the pattern by adding a service function. Create app/services/health_service.py:
def get_health_status() -> dict:
return {"status": "ok"}Then update app/routers/health.py to call the service:
from fastapi import APIRouter
from app.services.health_service import get_health_status
router = APIRouter(tags=["health"])
@router.get("/health")
def health_check() -> dict:
return get_health_status()This separation pays off when you add validation, persistence, or external calls: routers remain thin, services remain reusable, and tests can target services directly.
Run the App with Uvicorn (Reload Enabled)
From the project root (where app/ lives), run:
uvicorn app.main:app --reloadHow to read app.main:app:
app.mainis the Python module path (app/main.py).appis the variable inside that module that holds the FastAPI instance.
The --reload flag watches your files and restarts the server when you edit code. Use it for local development; in production you typically run without reload.
Verify the Health Check Endpoint
With the server running, open a browser or use curl:
curl http://127.0.0.1:8000/healthExpected response:
{"status":"ok"}OpenAPI Schema and Interactive Docs
FastAPI generates an OpenAPI schema automatically from your routes and Pydantic schemas.
- Raw OpenAPI JSON:
http://127.0.0.1:8000/openapi.json - Swagger UI (interactive docs):
http://127.0.0.1:8000/docs - ReDoc (alternative docs):
http://127.0.0.1:8000/redoc
After adding the /health route, you should see it listed under the health tag in the docs, and the OpenAPI schema will include a GET /health path entry.