This project is part of my backend portfolio demonstrating FastAPI, OAuth, and database integration.
A backend API server for organizing your YouTube subscriptions using custom categories.
Built with FastAPI, PostgreSQL, and Google OAuth 2.0 authentication.
- 🔐 Google OAuth 2.0 login with JWT issuance
- 📋 Add and manage YouTube subscriptions (manual input)
- 🗂 Create and assign custom categories to subscriptions
- 🔄 Modify or delete subscriptions and categories
- 🧾 Fully documented APIs via Swagger UI
- 🐳 Dockerized local development environment
- Backend: FastAPI, Pydantic, SQLAlchemy (Async)
- Auth: Google OAuth 2.0 + JWT
- Database: PostgreSQL + Alembic
- DevOps: Docker, docker-compose
- Docs: Swagger UI (
/docs), ReDoc (/redoc)
URL: Link
# 1. Create environment file
cp .env.example .env
# 2. Run the app
docker-compose up --build
# 3. Apply DB migrations
docker exec -it [backend-container-name] alembic upgrade headReplace [backend-container-name] with the actual container name (e.g. subscription-backend)
[User]
↓ Visit /login/google
[Google OAuth Consent Screen]
↓ Redirect to /auth/google/callback?code=...
[Backend]
- Exchange code → access_token
- Fetch user info
- Store user or fetch existing
- Issue JWT → return access_token
You will receive a response like:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer"
}
Copy the access_token, then in Swagger UI:
- Click the Authorize button
- enter:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6...
- Now you can access protected endpoints.
Once the server is running, open:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
- Note: /auth/google/callback is not intended to be called manually from Swagger. It is triggered automatically after Google login.
- Visit /login/google to log in
- After redirection, copy the access_token from the JSON response
- In Swagger, click Authorize → enter:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6...
- You can now access all protected endpoints like:
- POST /channels/
- GET /channels/
- PATCH /channels/{id}/category
- DELETE /categories/{id}
Main entities:
- users: OAuth-authenticated users
- channels: YouTube channels (shared across users)
- categories: User-defined tags
- user_channel_subscriptions: User-to-channel link (with optional category)
┌────────────────────────────────┐
│ users │
├────────────────────────────────┤
│ id PK │
│ email ✔︎ UNIQUE │
│ name Optional │
│ picture Optional │
│ google_id ✔︎ UNIQUE │
└────────────────────────────────┘
▲ 1 ▲ 1
│ │
│ │
┌────┴──────┐ ┌─────┴──────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────┐ ┌────────────────────────────────────────────┐
│ categories │ │ user_channel_subscriptions │
├─────────────────────┤ ├────────────────────────────────────────────┤
│ id PK │ │ id PK │
│ name ✔︎ │ │ user_id ✔︎ FK → users.id │
│ user_id ✔︎ FK │ │ channel_id ✔︎ FK → channels.id │
└─────────────────────┘ │ category_id Optional FK → categories.id │
└────────────────────────────────────────────┘
▲
│
│
┌─────────┴────────────┐
│ │
▼ ▼
┌──────────────────────────────────┐
│ channels │
├──────────────────────────────────┤
│ id PK │
│ youtube_id ✔︎ UNIQUE │
│ title ✔︎ │
│ thumbnail_url Optional │
│ description Optional │
└──────────────────────────────────┘
| Symbol | Meaning |
|---|---|
| PK | Primary Key |
| FK | Foreign Key |
| ✔︎ | Required (nullable=False) |
| Optional | Optional field (nullable=True) |
| UNIQUE | Uniqueness constraint (no duplicates) |
| Relationship | Description |
|---|---|
| users 1 ────▶ N categories | A user can create multiple categories |
| users 1 ────▶ N user_channel_subscriptions | A user can subscribe to multiple channels |
| channels 1 ────▶ N user_channel_subscriptions | A channel can be subscribed by many users |
| categories 1 ────▶ N user_channel_subscriptions (nullable) | A subscription may or may not be assigned to a category |
app/
├── api/ # API endpoints
│ └── endpoints/ # channel, category, auth routes
├── core/ # config, OAuth, JWT utils
├── db/ # DB session
├── models/ # SQLAlchemy ORM models
├── schemas/ # Pydantic models
├── services/ # Business logic
├── main.py # FastAPI app entrypoint
alembic/ # Alembic migrations
Dockerfile
docker-compose.yml
.env.example
- Save the example environment file to the root directory and create your actual
.envfile:
cp .env.example .env
| Variable | Description |
|---|---|
GOOGLE_CLIENT_ID |
Google OAuth Client ID from Google Cloud |
GOOGLE_CLIENT_SECRET |
OAuth Client Secret from Google Cloud |
GOOGLE_REDIRECT_URI |
Must match the one registered in Google Console |
JWT_SECRET_KEY |
Secret key used to sign JWT tokens |
DATABASE_URL |
Connection URL for PostgreSQL (via docker) |
This project uses live Google OAuth 2.0, so you'll need to register your own credentials for testing.
MIT