GET /api/v1/videos/by-uploader/{uploader_id} - Videos by Uploader
Overview
This endpoint returns all videos submitted by a specific user. It is a classic secondary access pattern — the primary key for the videos table is videoid, but here we need to query by userid. This requires either a secondary index or a dedicated denormalized table.
Why it exists: User profile pages need to display a creator's video portfolio. Without this endpoint, there would be no way to show "all videos by this person."
HTTP Details
- Method: GET
- Path:
/api/v1/videos/by-uploader/{uploader_id} - Auth Required: No (public endpoint)
- Success Status: 200 OK
Path Parameters
| Parameter | Type | Description |
|---|---|---|
uploader_id |
UUID | The user whose videos to fetch |
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number |
pageSize |
integer | 9 | Results per page (max 20) |
Request
GET /api/v1/videos/by-uploader/a1b2c3d4-e5f6-7890-abcd-ef1234567890?page=1&pageSize=9
Response Body
{
"items": [
{
"videoId": "550e8400-e29b-41d4-a716-446655440000",
"userId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Introduction to Apache Cassandra",
"previewImageLocation": "https://img.youtube.com/vi/abc123/mqdefault.jpg",
"addedDate": "2025-10-31T10:30:00Z"
}
],
"total": 7,
"page": 1,
"pageSize": 9
}
Cassandra Concepts Explained
Secondary Access Patterns
In relational databases, you can add a WHERE clause on any column and the database figures out how to resolve it (using indexes, table scans, etc.). In Cassandra, every query must include the partition key — unless you have a secondary index.
The videos table is partitioned by videoid. To query by userid, you have two options:
Option 1: Denormalized table
CREATE TABLE killrvideo.user_videos (
userid uuid,
videoid uuid,
added_date timestamp,
-- summary fields for display
PRIMARY KEY ((userid), added_date, videoid)
) WITH CLUSTERING ORDER BY (added_date DESC, videoid ASC);
Every video insert also inserts a row here. Queries by userid are direct partition key lookups.
Option 2: SAI on userid in videos table
CREATE CUSTOM INDEX videos_userid_idx
ON killrvideo.videos(userid)
USING 'StorageAttachedIndex';
Query the videos table directly with WHERE userid = ?.
KillrVideo uses the SAI approach for simplicity. The tradeoff is that the SAI query is slightly less efficient than a partition key lookup, but it eliminates the need to maintain a separate table.
SAI on a Non-Primary-Key Column
An SAI index on userid enables filtering by uploader without a full table scan. Under the hood:
- Cassandra's SAI index maintains a sorted list of
(userid → token)mappings - A query
WHERE userid = ?looks up the userid in this index to find which tokens (and thus which partitions) match - Those partitions are fetched directly
This is efficient even for users with many videos because the index narrows the search space before any actual row reading.
The Trade-off: Denormalized Table vs. SAI
| Approach | Read Performance | Write Complexity | Storage |
|---|---|---|---|
Denormalized user_videos table |
Fastest (partition key) | High (two writes) | 2x |
SAI on videos.userid |
Fast (indexed) | Low (one write) | ~1.3x |
For a learning platform like KillrVideo, the SAI approach is preferred for its simplicity. A high-scale production system might use the denormalized table approach.
Data Model
Option A: Denormalized Table (alternative design)
CREATE TABLE killrvideo.user_videos (
userid uuid,
added_date timestamp,
videoid uuid,
name text,
preview_image_location text,
PRIMARY KEY ((userid), added_date, videoid)
) WITH CLUSTERING ORDER BY (added_date DESC, videoid ASC);
Option B: SAI on videos (current approach)
CREATE TABLE killrvideo.videos (
videoid uuid PRIMARY KEY,
userid uuid,
name text,
preview_image_location text,
added_date timestamp,
status text
-- ... other columns
);
-- SAI index enables filtering by uploader
CREATE CUSTOM INDEX videos_userid_idx
ON killrvideo.videos(userid)
USING 'StorageAttachedIndex';
Database Queries
Query: Find Videos by Uploader
Equivalent CQL:
SELECT videoid, userid, name, preview_image_location, added_date
FROM killrvideo.videos
WHERE userid = a1b2c3d4-e5f6-7890-abcd-ef1234567890
AND status = 'READY'
ORDER BY added_date DESC
LIMIT 9
ALLOW FILTERING;
Note: ORDER BY in CQL requires the column to be a clustering column. With SAI on a non-clustering column, ordering is handled at the application layer by sorting the result set.
Implementation Flow
┌──────────────────────────────────────────────────────────┐
│ 1. Client sends GET /api/v1/videos/by-uploader/{id} │
│ ?page=1&pageSize=9 │
└────────────────────┬─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 2. Validate UUID format of uploader_id │
│ └─ Invalid format? → 422 │
└────────────────────┬─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 3. Query videos WHERE userid = ? AND status = 'READY' │
│ with skip and limit for pagination │
└────────────────────┬─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 4. Sort results by added_date DESC (application layer) │
└────────────────────┬─────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 5. Assemble PaginatedResponse and return 200 OK │
└──────────────────────────────────────────────────────────┘
Special Notes
1. Empty Result for Unknown User
If no videos are found for the given uploader_id, the response is:
{ "items": [], "total": 0, "page": 1, "pageSize": 9 }
This is NOT a 404. The uploader may exist but have no videos, or they may not exist at all. The endpoint returns an empty list in both cases.
2. Only READY Videos Are Shown
Like GET /api/v1/videos/by-tag/{tag}, only videos with status = 'READY' are shown publicly. A creator's own profile page might want to show PENDING and ERROR videos too — that would require a separate authenticated endpoint.
3. Large Uploader Libraries
A prolific creator might have hundreds or thousands of videos. SAI handles this gracefully with proper pagination. The skip parameter in the Data API is used to implement page offsets:
- Page 1:
skip=0, limit=9 - Page 2:
skip=9, limit=9
4. Creator Context
This endpoint is often displayed alongside the uploader's profile, fetched from GET /api/v1/users/{user_id}. The two calls can be made in parallel:
GET /users/{uploader_id} (profile details)
GET /videos/by-uploader/{id} (video list)
Developer Tips
Common Pitfalls
-
Returning 404 for users with no videos: The correct response is 200 with an empty items array.
-
Not filtering by READY status: Showing PENDING videos publicly exposes incomplete content.
-
N+1 when displaying uploader names: If listing videos from multiple uploaders, avoid fetching each user separately. Batch the user lookups.
-
Assuming sorted order: SAI results may not be sorted by
added_date. Sort in application code if ordering matters.
Best Practices
-
Show video count on profile pages: Fetch the total from this endpoint to display "X videos" without listing all of them.
-
Combine with user lookup: Fetch user profile and video list in parallel to minimize page load time.
-
Consider dedicated
user_videostable for high-volume creators: If any creator has tens of thousands of videos, the SAI approach may become less efficient than a dedicated denormalized table. -
Cache per-uploader results: The first page of a creator's videos is a good caching target (TTL of 1–5 minutes).
Performance Expectations
| Scenario | Latency | Notes |
|---|---|---|
| User with few videos | 5–15ms | SAI lookup + small result set |
| User with many videos (paginated) | 10–20ms | SAI + larger index scan |
| Empty result (user has no videos) | 5–10ms | Quick empty result |
Related Endpoints
- GET /api/v1/users/{user_id} - Uploader's profile details
- GET /api/v1/videos/{id} - Individual video details
- GET /api/v1/videos/latest - All users' latest videos