Bart Dorsey

Project Setup & Configuration

MinIO vs AWS S3

This project uses MinIO, an open-source self-hosted object storage server that is fully compatible with the S3 API. You can run and test everything locally without an AWS account or cloud costs.

What is an object storage server?

Basically it’s just a place on the web to store files, and then get publicly accessible URLs for those files. The most popular way to do this is with Amazon’s S3 service. But this service has become so popular that many other services have implemented the S3 API. For instance, Minio, Backblaze B2, and Cloudflare C2 all implement the S3 API.

To switch to AWS S3 later, you only need to update your configuration:

No code changes required — MinIO implements the same API as AWS S3.


Starting the Services

The project uses Docker Compose to run MinIO and PostgreSQL locally:

cd backend
docker compose up -d

This starts:

You can access the MinIO web console at http://localhost:9001 to browse uploaded files.


Backend Setup

Follow these steps to setup the backend FastAPI server.

cd backend
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.sample .env

Edit .env with your MinIO credentials (the defaults from docker-compose.yml will work for local development), then start the API:

fastapi dev

The API will be available at http://localhost:8000. Visit http://localhost:8000/docs for the interactive API docs.


Frontend Setup

Run this to setup the frontend. This frontend uses vite with the React TypeScript template.

cd frontend
npm install
npm run dev

The frontend will be available at http://localhost:5173.


Configuration with Environment Variables

All sensitive configuration lives in config.py, which reads from environment variables so secrets are never hard-coded in source:

backend/config.py on GitHub

# config.py
import os
from dotenv import load_dotenv

success = load_dotenv()
if not success:
    print("Warning: .env file not found or couldn't be loaded.")

# Database
DATABASE_URL = os.environ.get(
    "DATABASE_URL",
    "postgresql+psycopg://postgres:postgres@localhost:5432/photos",
)

# S3 / MinIO
AWS_ACCESS_KEY = os.environ.get("AWS_ACCESS_KEY")
AWS_SECRET_KEY = os.environ.get("AWS_SECRET_KEY")
S3_PUBLIC_URL = os.environ.get("S3_PUBLIC_URL", "http://localhost:9000")
S3_ENDPOINT_URL = os.environ.get("S3_ENDPOINT_URL", "http://localhost:9000")
BUCKET_NAME = "photos"

if AWS_ACCESS_KEY is None or AWS_SECRET_KEY is None:
    msg = "AWS_ACCESS_KEY and AWS_SECRET_KEY must be defined in .env file."
    raise ValueError(msg)

# CORS — comma-separated origins
cors_origins_value = os.environ.get("CORS_ORIGINS", "http://localhost:5173")
CORS_ORIGINS = cors_origins_value.split(",")

What’s that CORS business?

CORS stands for Cross Origin Resource Sharing. JavaScript inside your browser can only access URLs on the same domain by default. We need CORS here because our frontend and backend are running on different ports. This allows the frontend running on port 5173 to access the backend FastAPI server running on 8000.

Wait, what even are environment variables?

Think of environment variables as operating system level variables. When you run your program on different computers, these are values that need to change when you are running on a different machine. Often we store secret values here, because they will need to be different between our dev environment and our production environment. A handy way to set them is to use the dotenv package, which reads them from a .env file that we list in our gitignore so we don’t accidentally check them in.

The .env file

Copy .env.sample to .env and fill in your values:

AWS_ACCESS_KEY=minioadmin
AWS_SECRET_KEY=minioadmin
S3_ENDPOINT_URL=http://localhost:9000
S3_PUBLIC_URL=http://localhost:9000
DATABASE_URL=postgresql+psycopg://postgres:postgres@localhost:5432/photos
CORS_ORIGINS=http://localhost:5173

The .env file should be in .gitignore — never commit it.