Skip to content

Project Layout

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.

TypeScript
# Express / NestJS project
my-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
Python
# FastAPI project
my-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
└── .env

The mapping is almost 1:1:

  • controllerrouters/ (uses APIRouter)
  • service → you can add a services/ layer — FastAPI does not enforce it
  • dtoschemas/ (Pydantic models)
  • entitymodels/ (SQLAlchemy models)
  • app.module.tsmain.py
TypeScript
// Express src/main.ts
import 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'));
Python
# app/main.py
from contextlib import asynccontextmanager
from fastapi import FastAPI
from 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).
@asynccontextmanager
async 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 --reload

pyproject.toml — the package.json equivalent

Section titled “pyproject.toml — the package.json equivalent”
TypeScript
// 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"
}
}
Python
# 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 --reload

Run this locally — needs FastAPI + a server. Create the layout above, install deps with pip install -e ".[dev]" or uv sync, then uvicorn app.main:app --reload.

In a FastAPI project, Pydantic schemas (request/response shapes) are conventionally placed in which directory?
What is the FastAPI equivalent of registering a NestJS module with `app.module.ts`?
Which file in a modern Python project serves the same role as `package.json`?