Project Layout
How the file tree compares
Section titled “How the file tree compares”A NestJS or Express project has its own conventions. FastAPI has none enforced by the framework — but the community has settled on a pattern that will feel familiar.
# Express / NestJS projectmy-api/├── src/│ ├── main.ts # bootstrap│ ├── app.module.ts # root module (NestJS)│ ├── items/│ │ ├── items.controller.ts│ │ ├── items.service.ts│ │ └── items.dto.ts│ └── database/│ └── database.module.ts├── test/├── package.json└── tsconfig.json# FastAPI projectmy-api/├── app/│ ├── main.py # FastAPI() instance + lifespan│ ├── routers/│ │ └── items.py # APIRouter — like a controller│ ├── models/│ │ └── item.py # SQLAlchemy ORM models│ ├── schemas/│ │ └── item.py # Pydantic schemas (DTOs)│ ├── dependencies.py # shared Depends() factories│ └── database.py # engine + Session├── tests/├── pyproject.toml # like package.json└── .envThe mapping is almost 1:1:
controller→routers/(usesAPIRouter)service→ you can add aservices/layer — FastAPI does not enforce itdto→schemas/(Pydantic models)entity→models/(SQLAlchemy models)app.module.ts→main.py
main.py — the entry point
Section titled “main.py — the entry point”// Express src/main.tsimport express from 'express';import { itemsRouter } from './items/items.router';
const app = express();app.use(express.json());app.use('/items', itemsRouter);
app.listen(3000, () => console.log('listening on 3000'));# app/main.pyfrom contextlib import asynccontextmanager
from fastapi import FastAPIfrom app.routers import items
# Startup/shutdown via lifespan: code before yield runs at startup,# code after yield runs at shutdown (e.g. open/close a DB pool).@asynccontextmanagerasync def lifespan(app: FastAPI): print("Server is starting up") yield print("Server is shutting down")
app = FastAPI(title="Items API", version="1.0.0", lifespan=lifespan)
app.include_router(items.router, prefix="/items", tags=["items"])
# Run: uvicorn app.main:app --reloadpyproject.toml — the package.json equivalent
Section titled “pyproject.toml — the package.json equivalent”// package.json{ "name": "my-api", "version": "1.0.0", "scripts": { "start": "node dist/main.js", "dev": "ts-node-dev src/main.ts", "build": "tsc" }, "dependencies": { "express": "^4.18.0", "zod": "^3.22.0" }, "devDependencies": { "typescript": "^5.0.0", "@types/express": "^4.17.0" }}# pyproject.toml (PEP 517/518 — used by pip / poetry / uv)[project]name = "my-api"version = "1.0.0"requires-python = ">=3.11"dependencies = [ "fastapi>=0.115", "uvicorn[standard]>=0.29", "pydantic>=2.0", "sqlalchemy>=2.0",]
[project.optional-dependencies]dev = ["pytest", "httpx", "mypy"]
[tool.uvicorn]# Start: uvicorn app.main:app --reload# or: python -m uvicorn app.main:app --reloadRun this locally — needs FastAPI + a server. Create the layout above, install deps with
pip install -e ".[dev]"oruv sync, thenuvicorn app.main:app --reload.