# Set up a FastAPI project with uv

Most FastAPI tutorials have you install Python, create a virtual environment, and pip-install three packages before you write your first endpoint. This tutorial uses [uv](https://pydevtools.com/handbook/reference/uv.md) for the entire setup: Python install, {{< term "virtual-environment" "virtual environment" >}}, dependencies, and locking. By the end you will have a working API with tests and linting.

## Prerequisites

[Install uv on your system.](https://pydevtools.com/handbook/how-to/how-to-install-uv.md)

## Create the project

```console
$ uv init --no-package --python 3.13 taskapi
Initialized project `taskapi` at `/path/to/taskapi`
$ cd taskapi
```

`uv init` writes the starter files for the project. `--no-package` keeps the project layout flat (a `main.py` at the top level instead of a `src/` package). `--python 3.13` pins the {{< term "interpreter" >}} so the tutorial is reproducible regardless of what your system has installed.

{{< /filetree/folder >}}
{{< /filetree/container >}}

Open [pyproject.toml](https://pydevtools.com/handbook/reference/pyproject.toml.md):

```toml
[project]
name = "taskapi"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = []
```

> [!NOTE]
> Without `--python 3.13`, uv writes whichever Python it finds newest on your machine into `requires-python`. On a machine with Python 3.14 installed, that produces `requires-python = ">=3.14"`, which can later block `uv python pin 3.13`. Pinning at init time avoids that conflict.

## Add FastAPI

```console
$ uv add "fastapi[standard]"
Using CPython 3.13.6
Creating virtual environment at: .venv
Installed 43 packages in 55ms
 + fastapi==0.141.1
 + uvicorn==0.52.1
 ...
```

The `[standard]` extra bundles uvicorn (the ASGI server that runs your app), watchfiles (for auto-reload during development), and httptools. Without it, `fastapi dev` would not be available and you would need to install uvicorn separately.

`pyproject.toml` now records the {{< term "dependency" >}}:

```toml
dependencies = [
    "fastapi[standard]>=0.141.1",
]
```

> [!TIP]
> Commit `pyproject.toml` and `uv.lock` to version control, and add `.venv/` to `.gitignore` (uv already generates a `.gitignore` that covers it). The {{< term "lockfile" >}} guarantees teammates recreate the same environment with `uv sync`.

## Write a minimal API

Open `main.py` and replace its contents:

```python {filename="main.py"}
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

tasks = []


class Task(BaseModel):
    name: str
    done: bool = False


@app.get("/tasks")
def list_tasks():
    return tasks


@app.post("/tasks", status_code=201)
def create_task(task: Task):
    tasks.append(task)
    return task
```

`app = FastAPI()` creates the application instance that uvicorn looks for. The `Task` model uses Pydantic, which FastAPI includes as a dependency, to validate incoming JSON. Two endpoints handle the basics: `GET /tasks` returns the current list, and `POST /tasks` adds a new item after validating the request body against the `Task` schema.

## Start the development server

`uv run` executes the command inside the project's virtual environment, ensuring all dependencies are installed first.

```bash
uv run fastapi dev
```

The server starts with auto-reload enabled:

```console
$ uv run fastapi dev
⚡️ Starting FastAPI in development mode

 🐍 Using import string: main:app

 🌐 Server started at http://127.0.0.1:8000
    Documentation at http://127.0.0.1:8000/docs

  Logs:

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [28643] using WatchFiles
```

Visit <http://127.0.0.1:8000/docs> in a browser to see the interactive API documentation FastAPI generates from your endpoint signatures and Pydantic models. Try the `POST /tasks` endpoint from the docs page by clicking "Try it out", entering `{"name": "Buy groceries"}`, and hitting "Execute". Then call `GET /tasks` to confirm the item was stored.

Press `CTRL+C` to stop the server.

> [!NOTE]
> `fastapi dev` auto-discovers `main:app` by scanning for a `FastAPI()` instance in `main.py`. If you rename the file or the variable, pass the import string explicitly: `uv run fastapi dev myapp.py`.

## Add tests

FastAPI ships a `TestClient` that sends requests to your app without starting a real server. Add [pytest](https://pydevtools.com/handbook/reference/pytest.md) as a {{< term "dev-dependency" "development dependency" >}}:

```console
$ uv add --dev pytest
Resolved 49 packages in 327ms
Installed 4 packages in 5ms
 + iniconfig==2.3.0
 + packaging==26.3
 + pluggy==1.6.0
 + pytest==9.1.1
```

The `--dev` flag marks pytest as a development dependency, keeping it out of production installs. httpx, which `TestClient` uses under the hood, is already installed because `fastapi[standard]` pulls it in.

Because this is a `--no-package` project (flat layout with `main.py` at the root), pytest cannot import your application module by default. Tell it where to look by adding a `pythonpath` setting to `pyproject.toml`:

```toml
[tool.pytest.ini_options]
pythonpath = ["."]
```

Without this line, `from main import app` in the test file fails with `ModuleNotFoundError: No module named 'main'`.

Create a `tests/` directory and a test file:

```console
$ mkdir tests
```

```python {filename="tests/test_main.py"}
from fastapi.testclient import TestClient

from main import app, tasks

client = TestClient(app)


def test_list_tasks_empty():
    tasks.clear()
    response = client.get("/tasks")
    assert response.status_code == 200
    assert response.json() == []


def test_create_task():
    tasks.clear()
    response = client.post("/tasks", json={"name": "Write tests"})
    assert response.status_code == 201
    assert response.json() == {"name": "Write tests", "done": False}
```

`TestClient(app)` wraps your FastAPI application so you can call `.get()` and `.post()` on it directly. Each test calls `tasks.clear()` first so tests run independently regardless of execution order.

Run the tests:

```console
$ uv run pytest tests/ -v
============================= test session starts ==============================
platform darwin -- Python 3.13.6, pytest-9.1.1, pluggy-1.6.0
rootdir: /path/to/taskapi
configfile: pyproject.toml
plugins: anyio-4.14.2
collected 2 items

tests/test_main.py::test_list_tasks_empty PASSED                         [ 50%]
tests/test_main.py::test_create_task PASSED                              [100%]

============================== 2 passed in 0.19s ===============================
```

Both tests pass.

## Add Ruff for linting and formatting

Add [Ruff](https://pydevtools.com/handbook/reference/ruff.md) as a development dependency:

```console
$ uv add --dev ruff
Resolved 50 packages in 189ms
Installed 1 package in 2ms
 + ruff==0.16.2
```

Check the project for lint violations and formatting issues:

```console
$ uv run ruff check .
All checks passed!
```

Both pass on the code written so far because the tutorial's examples follow Ruff's defaults. See [Set up Ruff for formatting and checking your code](https://pydevtools.com/handbook/tutorial/set-up-ruff-for-formatting-and-checking-your-code.md) for configuring rule sets and auto-fix.

## Review the project structure

This flat layout works for small APIs. As a project grows, move the application code into a package:

{{< /filetree/folder >}}
    {{< /filetree/folder >}}
    {{< /filetree/folder >}}
  {{< /filetree/folder >}}
{{< /filetree/container >}}

To switch to that layout, re-initialize with `uv init --package` (or create the `src/` tree manually and add a `[build-system]` table to `pyproject.toml`). With a package layout, uv installs your project into the venv, so `from taskapi.main import app` works in tests without the `pythonpath` workaround.

## Run in production

`fastapi dev` enables auto-reload and binds to `127.0.0.1`, both suited for local work. For production, use `fastapi run`:

```console
$ uv run fastapi run main.py
⚡️ Starting FastAPI in production mode

 🐍 Using import string: main:app

 🌐 Server started at http://0.0.0.0:8000
    Documentation at http://0.0.0.0:8000/docs
```

`fastapi run` disables auto-reload and binds to `0.0.0.0` so the server accepts connections from other machines.

> [!WARNING]
> `0.0.0.0` accepts connections from any network interface. In production, place a reverse proxy like nginx or Caddy in front of uvicorn.

For finer control (worker count, timeouts, TLS), call uvicorn directly:

```bash
uv run uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
```

`--workers 4` spawns four uvicorn worker processes for handling concurrent requests. See [How to use uv in a Dockerfile](https://pydevtools.com/handbook/how-to/how-to-use-uv-in-a-dockerfile.md) for containerizing this setup.

## Reproduce the environment

A teammate cloning the repo runs one command:

```console
$ uv sync
Using CPython 3.13.6
Creating virtual environment at: .venv
Resolved 50 packages in 2ms
Installed 47 packages in 92ms
 + fastapi==0.141.1
 + pytest==9.1.1
 + ruff==0.16.2
 + uvicorn==0.52.1
 ...
```

uv reads `.python-version` and `uv.lock`, downloads Python 3.13 if needed, creates `.venv/`, and installs the same package versions you developed against. From there they have the same `uv run fastapi dev` and `uv run pytest` you do.

## Next steps

- [FastAPI official tutorial](https://fastapi.tiangolo.com/tutorial/) builds out routes, databases, authentication, and background tasks. Run every command with `uv run` instead of bare `python`.
- [Setting up testing with pytest and uv](https://pydevtools.com/handbook/tutorial/setting-up-testing-with-pytest-and-uv.md) covers parametrized tests and code coverage.
- [Set up Ruff for formatting and checking your code](https://pydevtools.com/handbook/tutorial/set-up-ruff-for-formatting-and-checking-your-code.md) walks through Ruff's full rule set, auto-fix, and pre-commit integration.
- [Setting up GitHub Actions with uv](https://pydevtools.com/handbook/tutorial/setting-up-github-actions-with-uv.md) runs tests and linting on every push.
- [How to use uv in a Dockerfile](https://pydevtools.com/handbook/how-to/how-to-use-uv-in-a-dockerfile.md) containerizes a uv-managed project for deployment.
- [uv's FastAPI integration guide](https://docs.astral.sh/uv/guides/integration/fastapi/) covers Docker deployment patterns.
