A modern, full-stack cloud storage and media sharing platform built with FastAPI and Streamlit. Upload images and videos, share them with the world, and manage your content — all powered by cloud infrastructure.
- JWT Authentication — Secure register/login flow using FastAPI Users with bcrypt + argon2 password hashing
- Media Uploads — Support for images (PNG, JPG, JPEG) and videos (MP4, AVI, MOV, MKV, WEBM)
- Cloud Storage — Files are uploaded directly to ImageKit.io for optimized delivery
- Social Feed — Browse all posts from every user, sorted by newest first
- Caption Overlays — ImageKit's transformation API renders captions directly on images
- Post Ownership — Users can only delete their own posts; enforced at the API level
- Async Backend — Fully async FastAPI + aiosqlite stack for high throughput
- Dark UI — Polished Streamlit interface with a custom dark theme
| Library | Purpose |
|---|---|
| FastAPI | High-performance async web framework |
| FastAPI Users | Auth, JWT, registration, password hashing |
| SQLAlchemy 2.0 | ORM + async session management |
| aiosqlite | Async SQLite driver |
| Uvicorn | ASGI server with hot-reload |
| ImageKit Python SDK | Cloud media upload and URL transformations |
| python-dotenv | Environment variable management |
| Library | Purpose |
|---|---|
| Streamlit | Rapid Python UI framework |
| Requests | HTTP client for calling the FastAPI backend |
| Component | Details |
|---|---|
| SQLite | Lightweight, file-based, zero-config |
| aiosqlite | Non-blocking async access |
| SQLAlchemy ORM | Declarative models, relationship management |
SQLAlchemy is the gold standard ORM for Python, and using it here — even with SQLite — was a deliberate choice for several reasons:
1. Async-first with SQLAlchemy 2.0
SQLAlchemy 2.0 introduced a fully async API (AsyncSession, create_async_engine). Combined with aiosqlite, all database operations are non-blocking, which keeps FastAPI's event loop free and maximises concurrency.
2. Declarative models keep schema close to code
Defining User and Post as Python classes (via DeclarativeBase) means the database schema is version-controlled alongside the application code. There's no separate migration file to remember to keep in sync.
class Post(Base):
__tablename__ = "posts"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id = Column(UUID(as_uuid=True), ForeignKey("user.id"), nullable=False)
caption = Column(Text)
url = Column(String, nullable=False)
...3. Relationships with zero raw SQL
SQLAlchemy's relationship() directive wires Post → User bidirectionally. Fetching a post's author, or all posts belonging to a user, happens through Python attribute access rather than hand-written JOINs.
4. FastAPI Users integration
FastAPI Users ships a first-class SQLAlchemy adapter (fastapi-users-db-sqlalchemy). Using SQLAlchemy means the full auth stack — registration, JWT login, password reset — is handled by battle-tested library code instead of custom logic.
5. Easy upgrade path Swapping SQLite for PostgreSQL in production requires changing exactly one line:
# Development
DATABASE_URL = "sqlite+aiosqlite:///./test.db"
# Production
DATABASE_URL = "postgresql+asyncpg://user:pass@host/db"The rest of the application is untouched.
cloudshare-rest-api/
├── app/
│ ├── __init__.py
│ ├── app.py # FastAPI app, route definitions
│ ├── db.py # SQLAlchemy models, engine, session factory
│ ├── schemas.py # Pydantic schemas (request/response validation)
│ ├── users.py # JWT strategy, auth backend, UserManager
│ └── images.py # ImageKit SDK initialisation
├── frontend.py # Streamlit UI
├── main.py # Uvicorn entry point
├── pyproject.toml # Project metadata & dependencies
├── uv.lock # Locked dependency tree
└── .gitignore
CREATE TABLE "user" (
id UUID PRIMARY KEY,
email VARCHAR NOT NULL UNIQUE,
hashed_password VARCHAR NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
is_superuser BOOLEAN DEFAULT FALSE,
is_verified BOOLEAN DEFAULT FALSE
);
CREATE TABLE posts (
id UUID PRIMARY KEY,
user_id UUID REFERENCES "user"(id) NOT NULL,
caption TEXT,
url VARCHAR NOT NULL, -- ImageKit CDN URL
file_type VARCHAR NOT NULL, -- 'image' or 'video'
file_name VARCHAR NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);- Python 3.13+
- A free ImageKit.io account
piporuv(recommended)
git clone https://github.com/yourusername/cloudshare-rest-api.git
cd cloudshare-rest-api# Using pip
pip install -e .
# Using uv (faster)
uv pip install -e .Create a .env file in the project root:
IMAGEKIT_PRIVATE_KEY=your_private_key_here
IMAGEKIT_PUBLIC_KEY=your_public_key_here
IMAGEKIT_URL=https://ik.imagekit.io/your_endpointGet your credentials from ImageKit Dashboard → Settings → API Keys.
python main.pyThe API is now live at http://localhost:8000
streamlit run frontend.pyThe UI opens at http://localhost:8501
| Interface | URL |
|---|---|
| Swagger UI | http://localhost:8000/docs |
| ReDoc | http://localhost:8000/redoc |
| Method | Endpoint | Description |
|---|---|---|
POST |
/auth/register |
Create a new account |
POST |
/auth/jwt/login |
Login and receive a JWT |
POST |
/auth/jwt/logout |
Invalidate the current session |
GET |
/users/me |
Return the authenticated user's profile |
| Method | Endpoint | Description |
|---|---|---|
POST |
/upload |
Upload a media file and create a post |
GET |
/feed |
Return all posts, newest first |
DELETE |
/posts/{post_id} |
Delete a post (owner only) |
User registers → Password hashed (argon2/bcrypt) → Stored in DB
↓
User logs in → JWT issued (1 hour lifetime)
↓
Frontend stores token in Streamlit session_state
↓
All API requests include: Authorization: Bearer <token>
↓
Backend validates token → Resolves current_active_user dependency
The JWT secret lives in app/users.py. Change this to a strong random value before deploying to production.
Uploaded files are streamed to ImageKit's CDN via the Python SDK. The returned CDN URL is saved to the database and served directly to clients — the application server never stores binary files.
Caption overlays are applied using ImageKit's URL-based transformation API:
/tr:l-text,ie-<base64_caption>,ly-N20,fs-100,co-white,bg-000000A0,l-end/
This encodes the caption as base64, overlays it at the bottom of the image with a semi-transparent background, and delivers it as a single optimised request.
Register
curl -X POST http://localhost:8000/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "secret123"}'Login
curl -X POST http://localhost:8000/auth/jwt/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=you@example.com&password=secret123"Get feed
curl http://localhost:8000/feed \
-H "Authorization: Bearer YOUR_TOKEN_HERE"Upload a post
curl -X POST http://localhost:8000/upload \
-H "Authorization: Bearer YOUR_TOKEN_HERE" \
-F "file=@photo.jpg" \
-F "caption=Hello world!"| Problem | Fix |
|---|---|
Connection refused on port 8000 |
Run python main.py first |
Upload failed error |
Check that ImageKit credentials in .env are correct |
Module not found |
Run pip install -e . first |
| JWT token expired | Log out and log back in to receive a fresh token |
| Database not created | It auto-creates on first startup; ensure write permissions in the project directory |