cd /news/developer-tools/modeling-a-creator-saas-in-a-single-โ€ฆ ยท home โ€บ topics โ€บ developer-tools โ€บ article
[ARTICLE ยท art-44018] src=dev.to โ†— pub= topic=developer-tools verified=true sentiment=ยท neutral

Modeling a Creator SaaS in a Single DynamoDB Table

A developer built Truss, a B2C SaaS for content creators, using a single DynamoDB table to model six entities including users, videos, clips, streams, analytics, and platform tokens. The single-table design was chosen over a relational database because the dominant read pattern is partition-scoped, DynamoDB's HTTP API fits serverless compute better than connection-pooled databases, and on-demand capacity scales to zero for a hackathon-born product. The schema uses a partition key of CREATOR#<userId> and sort keys with entity prefixes, enabling queries via begins_with without secondary indexes, though hot partitions and lack of ad-hoc querying are acknowledged trade-offs.

read4 min views1 publishedJun 29, 2026

Submitted to the AWS H0 Hackathon โ€” Vercel v0 + AWS Databases track. #H0Hackathon

Truss is a B2C SaaS for content creators. You upload a video or connect a live stream, and Truss uses Gemini to extract the highest-engagement moments, scores each for virality, and publishes 9:16 vertical clips across YouTube, TikTok, Twitch, and Discord.

The data model behind this sounds deceptively simple: users, videos, clips, streams, analytics, platform tokens. Six entities. But the access patterns are what matter โ€” and they pushed me toward a single-table DynamoDB design over a relational database in ways I didn't fully anticipate when I started.

The first question I get is always: why not just use a relational database?

Three reasons shaped this decision.

1. The dominant read pattern is partition-scoped. Almost every page in this app asks the same question: "Give me everything for user X, filtered by entity type." Dashboard needs recent assets and analytics. Clips page needs clips. Streams page needs streams. In SQL, that's joins or multiple round-trips. In DynamoDB with a single-table design, it's one Query

call per page โ€” no joins, no N+1 problems.

2. Serverless + connection pools don't mix well. Vercel functions are stateless and short-lived. Every cold start to a Postgres database burns 30โ€“80ms negotiating a connection before the first query runs. DynamoDB's HTTP API is connectionless by design โ€” it's a natural fit for serverless compute.

3. On-demand capacity. At launch, I have no idea how many creators will sign up. DynamoDB on-demand scales to zero (no idle cost) and scales up with zero capacity planning. For a hackathon-born product, that's the right default.

Everything lives in one DynamoDB table. Partition key: PK

(String). Sort key: SK

(String).

PK                        SK                              Entity
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€     โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€      โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
CREATOR#<userId>          METADATA                        Creator profile
CREATOR#<userId>          ASSET#<videoId>                 Uploaded video
CREATOR#<userId>          ASSET#<videoId>#CHAPTERS        AI-extracted chapters
CREATOR#<userId>          CLIP#<clipId>                   Extracted clip
CREATOR#<userId>          STREAM#<streamId>               Live stream record
CREATOR#<userId>          LIVE_CHAT_SPIKE#<timestamp>     Chat engagement spike
CREATOR#<userId>          ANALYTICS#DAILY#<date>          Daily metrics
CREATOR#<userId>          PLATFORM_TOKEN#<platform>       OAuth token
MAGIC#<token>             VERIFY                          Magic link token (TTL)

Every entity for a given user shares the same partition key. Queries use begins_with(SK, prefix)

to retrieve a specific entity type:

// All clips for a user
await docClient.send(new QueryCommand({
  TableName: TABLE,
  KeyConditionExpression: "PK = :pk AND begins_with(SK, :sk)",
  ExpressionAttributeValues: {
    ":pk": `CREATOR#${userId}`,
    ":sk": "CLIP#",
  },
}));

No GSIs. No secondary indexes. All access patterns are served by this one schema.

Single-table design has costs worth naming honestly.

Hot partitions are a real risk at scale. If one creator has 50,000 clips, their partition takes all the heat. DynamoDB on-demand handles burst well, but you can't fully escape this. For V2, I'd add a shard suffix to PK

for high-volume entities โ€” CREATOR#<userId>#CLIPS#<shard>

.

You lose ad-hoc querying. There's no SELECT * FROM clips WHERE viralityScore > 80

. Any access pattern you didn't model upfront requires a scan or a GSI. I've been careful to design the app's UI around the access patterns I built, not the other way around. That discipline is uncomfortable at first.

Magic link tokens use a different partition scheme (MAGIC#<token>

) because they need to be looked up by token, not by user. Mixing these in the same table is fine โ€” it's by design in single-table modeling โ€” but it requires deliberate thought about what "partition" means for each entity type.

The frontend is Next.js App Router on Vercel. A few things worth noting for other developers.

Route protection runs at the edge. A proxy.ts

middleware checks the NextAuth session cookie (authjs.session-token

) before any server component renders. Unauthenticated users get redirected to /login

at the CDN layer โ€” no compute wasted.

Video uploads bypass Vercel entirely. The browser requests a presigned S3 PUT URL from an API route, then uploads directly to S3. Vercel never sees a raw video byte. This avoids Vercel's response size limits and keeps egress costs at zero.

Credential federation, not static keys. In production, there are no AWS_ACCESS_KEY_ID

environment variables. Vercel injects an OIDC token; the app calls sts:AssumeRoleWithWebIdentity

to get short-lived credentials, cached in-process with a 60-second expiry buffer. This took an afternoon to set up and eliminated an entire category of credential-leak risk.

The analysis pipeline is currently synchronous โ€” it blocks the HTTP request while Gemini processes the video. For anything longer than a short clip, that's a race against Vercel's function timeout. The next version will push analysis to a background queue (Vercel Queues) with status polling.

DynamoDB queries also have no pagination yet. For a creator with thousands of clips, every page load fetches the full partition. Adding Limit

  • ExclusiveStartKey

is straightforward โ€” it just wasn't the bottleneck at hackathon scale.

Truss is live at the-truss-app.vercel.app. The stack โ€” Next.js on Vercel, single-table DynamoDB, S3 for object storage, Vercel AI SDK for Gemini โ€” held up well under the build pressure of a hackathon.

If you're a developer considering this stack for a B2C SaaS: the single-table DynamoDB pattern has a real learning curve, but once your access patterns click into the schema, the operational simplicity pays for the upfront modeling cost many times over.

#H0Hackathon

โ”€โ”€ more in #developer-tools 4 stories ยท sorted by recency
โ”€โ”€ more on @truss 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain โ€” perfect for shipping the agent you just read about.

$git push zahid main
โ†’ Live at https://your-agent.zahid.host โœ“
Get free account โ†’ Pricing
from โ‚ฌ0/mo ยท no card required
LIVE [news/modeling-a-creator-sโ€ฆ] indexed:0 read:4min 2026-06-29 ยท โ€”