Quorentra

Quorentra FastAPI Foundation: Building from Zero — Part 4

Series: Building Quorentra CRM from Zero — A Modular, ChatGPT-Native AI CRM

Installing FastAPI, creating the first Quorentra backend application, introducing API versioning, building the health endpoint, running Uvicorn, and adding the first automated API test.

Quorentra FastAPI Foundation: Building from Zero — Part 4
Quorentra FastAPI Foundation: Building from Zero — Part 4

1. Introduction

In Part 3, we created the canonical Quorentra repository.

We established:

quorentra/
├── backend/
├── frontend/
├── mcp/
├── scripts/
├── docs/
├── docker/
├── .env.example
├── .gitignore
└── README.md

We also created the Python virtual environment and established an important convention:

Backend commands are executed from the quorentra/backend directory.

Now we are going to create the first running Quorentra component.

By the end of this article, the following request will work:

GET /api/v1/health

and return:

JSON
{
"status": "healthy",
"service": "quorentra-api",
"version": "0.0.2"
}

That may appear modest compared with the eventual CRM architecture.

But it represents an important milestone.

For the first time:

Quorentra will be running software.


2. Starting Checkpoint

This article starts from the repository created in Part 3.

The relevant structure should be:

quorentra/
├── backend/
│ ├── .venv/
│ ├── app/
│ │ └── __init__.py
│ ├── tests/
│ └── README.md
├── frontend/
├── mcp/
├── scripts/
├── docs/
├── docker/
├── .env
├── .env.example
├── .gitignore
└── README.md

Before continuing, open PowerShell and navigate to the backend:

cd C:\Users\<your-user>\Documents\GitHub\quorentra\backend

Activate the virtual environment:

.\.venv\Scripts\Activate.ps1

Your prompt should resemble:

(.venv) PS C:\Users\<your-user>\Documents\GitHub\quorentra\backend>

Everything in this article assumes commands are executed from this directory unless stated otherwise.


3. Verify the Python Environment

Before installing anything, verify which Python interpreter is active.

Run:

python --version

Then:

where.exe python

The first path should point to something similar to:

C:\Users\<your-user>\Documents\GitHub\quorentra\backend\.venv\Scripts\python.exe

Also verify pip:

python -m pip --version

This prevents one of the most common Python development problems:

Installing packages into one Python environment while running the application with another.


4. Install the First Backend Dependencies

For this checkpoint, we only need a small dependency set.

Install FastAPI and Uvicorn:

python -m pip install fastapi "uvicorn[standard]"

We will also need testing support:

python -m pip install pytest httpx

Why httpx?

FastAPI’s testing infrastructure uses HTTPX for application-level HTTP testing.

At this point we have four important packages:

fastapi
uvicorn
pytest
httpx

We deliberately do not install SQLAlchemy, Alembic, PostgreSQL drivers, OpenAI libraries, MCP packages, or AI dependencies yet.

Those will be introduced when we actually need them.


5. Verify FastAPI Installation

Run:

python -m pip show fastapi

You should see package information.

Also verify Uvicorn:

python -m pip show uvicorn

And pytest:

python -m pytest --version

If these commands work, the environment is ready.


6. Create the First FastAPI Application

Our current application package contains:

backend/
└── app/
└── __init__.py

Create:

backend/app/main.py

From PowerShell:

New-Item app\main.py -ItemType File

The structure becomes:

backend/
└── app/
├── __init__.py
└── main.py

Open main.py.

Add:

Python
from fastapi import FastAPI
app = FastAPI(
title="Quorentra API",
description="Backend API for the Quorentra AI CRM.",
version="0.0.2",
)
@app.get("/")
async def root() -> dict[str, str]:
return {
"message": "Quorentra API",
"status": "running",
}

We now have the smallest useful FastAPI application.


7. Understanding the Application Object

The line:

app = FastAPI(...)

creates the ASGI application.

This object will become the central FastAPI application instance.

When we later run:

uvicorn app.main:app

the expression means:

app.main:app
│ │ │
│ │ └── FastAPI object
│ │
│ └── main.py
└── app Python package

In other words:

app/main.py

contains:

app = FastAPI(...)

Understanding this notation is important because incorrect module paths frequently cause:

Error loading ASGI app.
Could not import module "main".

8. Why We Run Uvicorn from backend

Our directory structure is:

quorentra/
└── backend/
└── app/
└── main.py

The Python package we want to import is:

app

Therefore our working directory should normally be:

quorentra/backend

From there Python can resolve:

import app

and Uvicorn can resolve:

app.main:app

This is why our canonical development command will be executed from backend.


9. Start Quorentra for the First Time

From:

quorentra/backend

with the virtual environment active, run:

python -m uvicorn app.main:app --reload

Using:

python -m uvicorn

rather than simply:

uvicorn

has one useful advantage during development.

It ensures Uvicorn runs through the currently selected Python interpreter.

You should see output similar to:

INFO: Uvicorn running on http://127.0.0.1:8000
INFO: Started reloader process
INFO: Started server process
INFO: Application startup complete.

Quorentra is now running.


10. Open Quorentra in the Browser

Open:

http://127.0.0.1:8000

You should receive:

{
"message": "Quorentra API",
"status": "running"
}

This is the first live response from the Quorentra backend.

We have reached:

Browser
FastAPI

The application does not yet have PostgreSQL, React, authentication, CRM modules, MCP, or AI.

That is fine.

The first component works.


11. FastAPI Interactive Documentation

FastAPI automatically generates OpenAPI documentation.

Open:

http://127.0.0.1:8000/docs

You should see the Swagger UI.

You can also open:

http://127.0.0.1:8000/redoc

for the alternative API documentation interface.

This automatic API documentation will become increasingly useful as Quorentra grows.


12. Why We Need API Versioning Early

Our root endpoint is useful for initial verification, but business APIs should live under a versioned namespace.

We will use:

/api/v1

Why version the API before we even have customers?

Because changing:

/api/companies

later into:

/api/v1/companies

creates unnecessary churn.

The cost of establishing the convention now is almost zero.

So our first real application endpoint will be:

GET /api/v1/health

13. Do Not Put Every Route in main.py

We could write:

Python
@app.get("/api/v1/health")

directly inside main.py.

That would work.

But if we continue doing this, main.py will eventually contain hundreds of routes.

Instead, we establish router separation immediately.

Create:

backend/app/api/

Then:

backend/app/api/v1/

From PowerShell:

mkdir app\api
mkdir app\api\v1

Create package files:

New-Item app\api\__init__.py -ItemType File
New-Item app\api\v1\__init__.py -ItemType File

Now create:

New-Item app\api\v1\health.py -ItemType File

The structure becomes:

backend/
└── app/
├── api/
│ ├── __init__.py
│ └── v1/
│ ├── __init__.py
│ └── health.py
├── __init__.py
└── main.py

14. Build the Health Router

Open:

app/api/v1/health.py

Add:

from fastapi import APIRouter
router = APIRouter()
@router.get("/health")
async def health_check() -> dict[str, str]:
return {
"status": "healthy",
"service": "quorentra-api",
"version": "0.0.2",
}

This router defines the health capability.

Notice that it does not yet contain:

/api/v1

That prefix will be applied centrally.


15. Create the API v1 Router

We do not want main.py to import every endpoint individually.

Create:

app/api/v1/router.py

From PowerShell:

New-Item app\api\v1\router.py -ItemType File

Add:

Python
from fastapi import APIRouter
from app.api.v1.health import router as health_router
api_router = APIRouter()
api_router.include_router(
health_router,
tags=["health"],
)

This router will eventually aggregate all v1 API modules.

Later it might look like:

api_router.include_router(auth_router, prefix="/auth")
api_router.include_router(companies_router, prefix="/companies")
api_router.include_router(contacts_router, prefix="/contacts")
api_router.include_router(opportunities_router, prefix="/opportunities")

But we only add routes when the associated modules exist.


16. Connect the Router to FastAPI

Now update:

app/main.py

to:

Python
from fastapi import FastAPI
from app.api.v1.router import api_router
app = FastAPI(
title="Quorentra API",
description="Backend API for the Quorentra AI CRM.",
version="0.0.2",
)
app.include_router(
api_router,
prefix="/api/v1",
)
@app.get("/")
async def root() -> dict[str, str]:
return {
"message": "Quorentra API",
"status": "running",
}

Because Uvicorn is running with:

--reload

it should automatically detect the file changes and restart.


17. Test the Health Endpoint

Open:

http://127.0.0.1:8000/api/v1/health

Expected response:

{
"status": "healthy",
"service": "quorentra-api",
"version": "0.0.2"
}

We now have:

Browser
FastAPI
API v1 Router
Health Endpoint

This is our first versioned Quorentra API.


18. Test Through Swagger UI

Open:

http://127.0.0.1:8000/docs

You should see the health section.

Expand:

GET /api/v1/health

Click:

Try it out

Then:

Execute

You should receive HTTP:

200

and the expected JSON response.


19. Improve the Health Response with a Schema

Returning:

dict[str, str]

works, but FastAPI is strongest when API contracts are explicit.

We will therefore introduce our first Pydantic response model.

Create:

app/api/v1/schemas.py

From PowerShell:

New-Item app\api\v1\schemas.py -ItemType File

Add:

from pydantic import BaseModel
class HealthResponse(BaseModel):
status: str
service: str
version: str

Now update:

app/api/v1/health.py

to:

from fastapi import APIRouter
from app.api.v1.schemas import HealthResponse
router = APIRouter()
@router.get(
"/health",
response_model=HealthResponse,
)
async def health_check() -> HealthResponse:
return HealthResponse(
status="healthy",
service="quorentra-api",
version="0.0.2",
)

This gives FastAPI an explicit API contract.


20. Why Explicit Response Models Matter

Later, Quorentra will return much more complex data.

For example:

CompanyResponse
ContactResponse
OpportunityResponse
TaskResponse
PipelineSummaryResponse

Explicit schemas give us:

  • validation;
  • predictable serialization;
  • generated OpenAPI documentation;
  • clearer API contracts;
  • better frontend integration;
  • safer MCP adaptation later.

This small health schema establishes a pattern we will reuse throughout the project.


21. Create the First Automated Test

Manual browser testing is useful, but it is not sufficient.

We want Quorentra’s development process to become testable from the beginning.

Our current test directory is:

backend/tests/

Create:

backend/tests/api/

From PowerShell:

mkdir tests\api

Create:

New-Item tests\__init__.py -ItemType File
New-Item tests\api\__init__.py -ItemType File
New-Item tests\api\test_health.py -ItemType File

The backend structure becomes:

backend/
├── app/
│ ├── api/
│ │ └── v1/
│ └── main.py
└── tests/
├── __init__.py
└── api/
├── __init__.py
└── test_health.py

22. Write the Health Test

Open:

tests/api/test_health.py

Add:

Python
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_health_check() -> None:
response = client.get("/api/v1/health")
assert response.status_code == 200
assert response.json() == {
"status": "healthy",
"service": "quorentra-api",
"version": "0.0.2",
}

This test starts the FastAPI application in-process and calls the endpoint.

It does not require Uvicorn to be running separately.


23. Run the First Quorentra Test

If Uvicorn is occupying the terminal, stop it with:

Ctrl + C

Make sure you remain in:

quorentra/backend

Run:

python -m pytest

Expected result should resemble:

collected 1 item
tests/api/test_health.py . [100%]
1 passed

Quorentra now has its first automated test.


24. Why python -m pytest?

You could run:

pytest

But throughout this series, we will often prefer:

python -m pytest

because it makes the interpreter relationship explicit.

The command means:

Run pytest using the currently active Python interpreter.

That reduces confusion when multiple Python installations exist on Windows.


25. Verify Python Imports

From:

quorentra/backend

run:

python -c "from app.main import app; print(app.title)"

Expected result:

Quorentra API

This small test confirms that the canonical package import works.

If this fails with:

ModuleNotFoundError: No module named 'app'

first check the current directory:

Get-Location

It should end with:

\quorentra\backend

This is precisely why we established the working-directory convention in Part 3.


26. Avoid This Uvicorn Command

From the repository root:

quorentra/

do not casually run:

uvicorn main:app --reload

There is no root-level:

main.py

Likewise, from backend, do not run:

uvicorn main:app --reload

because our application lives at:

app/main.py

The canonical command is:

python -m uvicorn app.main:app --reload

executed from:

quorentra/backend

Keeping this consistent avoids a large class of ASGI import errors.


27. Add a Development Server Script

We can now create our first utility script.

Return temporarily to the repository root:

cd ..

Create:

scripts/run_backend.ps1

using:

New-Item scripts\run_backend.ps1 -ItemType File

Add:

Set-Location "$PSScriptRoot\..\backend"
& ".\.venv\Scripts\python.exe" -m uvicorn app.main:app --reload

Now, from the repository root, you can run:

.\scripts\run_backend.ps1

This script deliberately sets the correct backend working directory before starting Uvicorn.

That gives developers two valid approaches.

From backend:

python -m uvicorn app.main:app --reload

Or from the repository root:

.\scripts\run_backend.ps1

28. Add a Test Script

Create:

scripts/test_backend.ps1

Add:

Set-Location "$PSScriptRoot\..\backend"
& ".\.venv\Scripts\python.exe" -m pytest

From the repository root:

.\scripts\test_backend.ps1

Expected result:

1 passed

These scripts will become useful as the project grows.


29. Create a Requirements File

We now have actual Python dependencies, so we should record them.

From the backend directory:

cd backend

Create:

requirements.txt

A simple first version can contain:

fastapi
uvicorn[standard]
pytest
httpx

For this tutorial series, we will initially keep the dependency file readable rather than dumping every transitive dependency into it.

Later, we can adopt more formal dependency locking if required.


30. Separate Runtime and Development Dependencies

As the project grows, it is useful to distinguish application dependencies from development/testing dependencies.

We can prepare for that now.

Create:

requirements.txt
requirements-dev.txt

Use:

requirements.txt

for runtime dependencies:

fastapi
uvicorn[standard]

Use:

requirements-dev.txt

for:

-r requirements.txt
pytest
httpx

This means a development environment can install everything with:

python -m pip install -r requirements-dev.txt

while a production environment can later install only:

python -m pip install -r requirements.txt

This is cleaner than mixing test tools into the production dependency set.


31. Test Dependency Reproducibility

We do not need to destroy the current virtual environment just to test this.

But the intended setup command for a new developer is now:

cd backend
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -r requirements-dev.txt

Then:

python -m pytest

This gives us the beginning of a reproducible backend setup.


32. Introduce Application Metadata

Instead of scattering values such as:

Quorentra API
0.0.2

through multiple files, we can centralize simple application metadata.

Create:

app/core/

From backend:

mkdir app\core
New-Item app\core\__init__.py -ItemType File
New-Item app\core\constants.py -ItemType File

Add to:

app/core/constants.py

the following:

APP_NAME = "Quorentra API"
APP_VERSION = "0.0.2"
APP_DESCRIPTION = "Backend API for the Quorentra AI CRM."

Now update main.py:

from fastapi import FastAPI
from app.api.v1.router import api_router
from app.core.constants import (
APP_DESCRIPTION,
APP_NAME,
APP_VERSION,
)
app = FastAPI(
title=APP_NAME,
description=APP_DESCRIPTION,
version=APP_VERSION,
)
app.include_router(
api_router,
prefix="/api/v1",
)
@app.get("/")
async def root() -> dict[str, str]:
return {
"message": APP_NAME,
"status": "running",
}

Update health.py:

from fastapi import APIRouter
from app.api.v1.schemas import HealthResponse
from app.core.constants import APP_VERSION
router = APIRouter()
@router.get(
"/health",
response_model=HealthResponse,
)
async def health_check() -> HealthResponse:
return HealthResponse(
status="healthy",
service="quorentra-api",
version=APP_VERSION,
)

Now the version has one authoritative location.


33. Why Not Build the Full Configuration System Yet?

Eventually Quorentra will have configuration such as:

DATABASE_URL
SECRET_KEY
ACCESS_TOKEN_EXPIRE_MINUTES
CORS_ORIGINS
OPENAI_API_KEY

We could introduce a full Pydantic Settings architecture now.

But there is no configuration-dependent behavior yet.

We will introduce that architecture when PostgreSQL and environment-driven settings arrive.

Again:

Complexity should arrive with the requirement that justifies it.


34. Run the Test Suite Again

Run:

python -m pytest

Expected:

1 passed

Then start the server again:

python -m uvicorn app.main:app --reload

Verify:

http://127.0.0.1:8000/

and:

http://127.0.0.1:8000/api/v1/health

Both should work.


35. Current Backend Structure

At the end of this article, the backend should resemble:

backend/
├── .venv/
├── app/
│ ├── __init__.py
│ │
│ ├── api/
│ │ ├── __init__.py
│ │ └── v1/
│ │ ├── __init__.py
│ │ ├── health.py
│ │ ├── router.py
│ │ └── schemas.py
│ │
│ ├── core/
│ │ ├── __init__.py
│ │ └── constants.py
│ │
│ └── main.py
├── tests/
│ ├── __init__.py
│ └── api/
│ ├── __init__.py
│ └── test_health.py
├── requirements.txt
├── requirements-dev.txt
└── README.md

And the root project contains:

scripts/
├── run_backend.ps1
├── test_backend.ps1
└── README.md

36. Our First Request Path

We can now describe the first real Quorentra request path.

Browser
Uvicorn
FastAPI
/api/v1
Health Router
HealthResponse
JSON

This is small.

But it establishes several patterns that will survive throughout the entire project:

  • ASGI application structure;
  • router separation;
  • API versioning;
  • Pydantic response contracts;
  • test organization;
  • dependency tracking;
  • canonical execution commands.

37. Run All Verification Checks

Stop Uvicorn if necessary:

Ctrl + C

From:

quorentra/backend

run:

python -c "from app.main import app; print(app.title)"

Expected:

Quorentra API

Run:

python -m pytest

Expected:

1 passed

Start:

python -m uvicorn app.main:app --reload

Open:

http://127.0.0.1:8000/api/v1/health

Expected:

{
"status": "healthy",
"service": "quorentra-api",
"version": "0.0.2"
}

If all four checks succeed, the implementation checkpoint is valid.


38. Troubleshooting

ModuleNotFoundError: No module named 'app'

First check:

Get-Location

You should be in:

quorentra\backend

Then verify:

python -c "import app; print(app)"

If this works, the package is visible.


Import "fastapi" could not be resolved

Check the selected VS Code Python interpreter.

It should be:

backend\.venv\Scripts\python.exe

Then verify from the VS Code terminal:

python -m pip show fastapi

If FastAPI is installed in the virtual environment but VS Code still reports the import as unresolved, reselect the interpreter or restart the Python language server.


Error loading ASGI app

Make sure the command is:

python -m uvicorn app.main:app --reload

and that it is executed from:

quorentra\backend

Verify:

app/main.py

exists and contains:

app = FastAPI(...)

Port 8000 Is Already in Use

Run on another port:

python -m uvicorn app.main:app --reload --port 8001

Then open:

http://127.0.0.1:8001

Pytest Cannot Import app

Again, verify that pytest is executed from:

quorentra/backend

using:

python -m pytest

Do not start by adding sys.path hacks to the tests.

Fix the environment or working directory instead.


39. Git Checkpoint

Stop Uvicorn.

Return to the repository root:

cd ..

Check:

git status

Review the files.

Make sure the virtual environment is not included.

Then:

git add .

Commit:

git commit -m "feat: add FastAPI application foundation"

Check:

git status

Expected:

nothing to commit, working tree clean

This is our second meaningful implementation checkpoint.


40. Quorentra Version

We can now consider the project:

Quorentra 0.0.2

The state is:

Repository ✓
Python env ✓
FastAPI ✓
API routing ✓
Health endpoint ✓
API schema ✓
Automated test ✓
PostgreSQL -
React -
Authentication -
CRM -
MCP -
ChatGPT -
AI -

For the first time, Quorentra itself is running.


41. Acceptance Criteria

Part 4 is complete when:

✓ FastAPI is installed
✓ Uvicorn is installed
✓ app/main.py exists
✓ FastAPI application starts
✓ GET / works
✓ API v1 routing exists
✓ GET /api/v1/health works
✓ Health response uses a Pydantic model
✓ Swagger documentation works
✓ pytest is configured
✓ Health endpoint test passes
✓ app imports correctly
✓ Runtime dependencies are recorded
✓ Development dependencies are recorded
✓ Backend run script works
✓ Backend test script works
✓ Git checkpoint exists

Most importantly:

The application must actually run before moving to Part 5.


42. What We Deliberately Did Not Add

We still have no:

  • PostgreSQL connection;
  • SQLAlchemy;
  • Alembic;
  • database models;
  • React frontend;
  • authentication;
  • CRM entities;
  • MCP server;
  • OpenAI integration.

That is intentional.

The current architecture is:

Developer
Uvicorn
FastAPI
Quorentra API

The next step is to give Quorentra persistence.


43. Next Article

In Part 5, we will build the PostgreSQL persistence foundation.

We will introduce:

FastAPI
SQLAlchemy
PostgreSQL

We will add:

  • environment-driven application configuration;
  • database URL configuration;
  • SQLAlchemy;
  • PostgreSQL driver;
  • engine creation;
  • session management;
  • database dependencies;
  • connection testing;
  • Alembic;
  • the first migration;
  • database-aware health checking.

The health architecture will evolve from:

FastAPI
healthy

to:

FastAPI
Database Health Check
SQLAlchemy
PostgreSQL

Our next acceptance criterion will therefore be stronger.

Instead of merely returning:

{
"status": "healthy"
}

Quorentra will have to prove that its database is actually reachable.

That will give us:

Quorentra 0.0.3 — API + Persistent Database Foundation

and move us one major step closer to the first Minimum Viable Operation:

React
FastAPI
PostgreSQL

The FastAPI foundation is now complete.

Next, Quorentra gets its database.

Quorentra

Contact

Menu

Designed with WordPress

Discover more from Quorentra

Subscribe now to keep reading and get access to the full archive.

Continue reading