DEV Community

Cover image for Day 55: Single Table Design for User Profiles in DynamoDB
Eric Rodríguez
Eric Rodríguez

Posted on

Day 55: Single Table Design for User Profiles in DynamoDB

Hardcoding variables is a developer habit. Building user configuration is a Product mindset. 🛠️

Up until today, my Serverless AI Financial Agent suffered from the classic "Minimum Viable Product" disease: hardcoded assumptions. The AI assumed a fixed €15 daily budget for everyone to maintain their savings streak, and it lazily parsed usernames directly from their Cognito JWT Token emails (e.g., turning ericridri11@gmail.com into "Ericridri11").

It worked for a proof of concept, but as a user experience? It was terrible.

For Day 55 of my #100DaysOfCloud challenge, it was time to mature the architecture and build a Stateful User Configuration Panel. The users need to take the wheel.

The Challenge: Database Bloat

The immediate thought of any developer is: "I need to save user preferences. Let's spin up a UserPreferences table in the database."

In relational databases (SQL), that's standard practice. But in the cloud, specifically with NoSQL databases like Amazon DynamoDB, compute is cheap but IOPS (Input/Output Operations Per Second) and maintaining multiple tables cost money and operational overhead.

How do we store custom user data without bloating our infrastructure?

The Solution: Single Table Design 🧠

Instead of provisioning a completely new DynamoDB table, I implemented an advanced NoSQL pattern called Single Table Design.

I overloaded my existing FinanceAgent-Transactions table. By querying a specific sort key formatted as PROFILE#{user_id}, I can retrieve a metadata record that acts as a container for user preferences, living right alongside their financial transactions.

With this single record, I can now store:
✅ Display Name (No more email parsing).
✅ Custom Daily Budget Goal (Dynamic streak calculation).
✅ Monthly Salary (For better FinOps forecasting).
✅ AI Personality Tone (Users can choose "Brutal", "Sarcastic", or "Polite").

Fun fact on the "Polite" tone: The app's core identity is "Tough Love". If a user selects "Polite", the system prompt instructs the AI to subtly mock them for being emotionally fragile and unable to handle the truth. The AI never loses!* 🤖

The Lambda Router

Since my entire backend runs on a single AWS Lambda Function URL, I had to upgrade my Python code to act as a basic RESTful router.

I added logic to intercept the HTTP method from the requestContext.

  • When the user hits "Save" on the React Frontend, a POST request is fired. The Lambda parses the JSON payload and updates the PROFILE record in DynamoDB.
  • When a standard GET request is made to load the dashboard, the Lambda fetches this profile first, and dynamically injects the user's real name and requested tone directly into the Amazon Nova Micro system prompt.

Here is a snippet of how I implemented the Profile extraction in Python using boto3:

def get_user_profile(user_id, default_name):
profile_id = f"PROFILE#{user_id}"
try:
response = table.get_item(Key={'user_id': user_id, 'transaction_date': profile_id})
if 'Item' in response:
return response['Item']
except Exception as e:
logger.error("Error fetching profile", extra={"details": str(e)})

# Return default schema if no profile exists yet
return {
    'user_id': user_id, 
    'transaction_date': profile_id, 
    'display_name': default_name, 
    'daily_budget': 15.00, 
    'ai_tone': 'brutal'
}
Enter fullscreen mode Exit fullscreen mode

The Lesson

Building the engine is only half the battle. Giving the driver a steering wheel is what makes it a real application. Think about your data access patterns before provisioning new cloud resources. Overloading NoSQL tables is an absolute superpower for keeping costs down while scaling up features.

Top comments (0)