<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Carolina</title>
    <description>The latest articles on DEV Community by Carolina (@alomarac3).</description>
    <link>https://dev.to/alomarac3</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1457699%2F927043a2-9581-4394-abdf-9d3a46c8aef2.png</url>
      <title>DEV Community: Carolina</title>
      <link>https://dev.to/alomarac3</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alomarac3"/>
    <language>en</language>
    <item>
      <title>Why I chose Aurora Serverless v2 + App Runner + Vercel for my SaaS stack</title>
      <dc:creator>Carolina</dc:creator>
      <pubDate>Fri, 26 Jun 2026 15:38:18 +0000</pubDate>
      <link>https://dev.to/alomarac3/why-i-chose-aurora-serverless-v2-app-runner-vercel-for-my-saas-stack-24k7</link>
      <guid>https://dev.to/alomarac3/why-i-chose-aurora-serverless-v2-app-runner-vercel-for-my-saas-stack-24k7</guid>
      <description>&lt;p&gt;Every stack decision in a solo-founder SaaS has a cost. Not just money — time, complexity, and cognitive load. When I started building InspectIQ for the H0 Hackathon, I had one rule: every technology choice had to earn its place.&lt;/p&gt;

&lt;p&gt;Here's what I chose and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aurora PostgreSQL Serverless v2
&lt;/h2&gt;

&lt;p&gt;The H0 Hackathon required an AWS database. I had three options: Aurora, Aurora DSQL, or DynamoDB.&lt;/p&gt;

&lt;p&gt;I chose Aurora PostgreSQL Serverless v2 for one reason that trumped everything else: Row Level Security.&lt;/p&gt;

&lt;p&gt;InspectIQ is multi-tenant from day one. Every inspector is a separate tenant with completely isolated data. RLS lets me enforce that isolation at the database layer, not the application layer. One missing WHERE clause in application code can't leak data between tenants when the database itself enforces the policy.&lt;/p&gt;

&lt;p&gt;Aurora Serverless v2 also scales to near-zero between requests. For an early-stage SaaS with unpredictable traffic, that matters. I'm not paying for a database that sits idle at 2am.&lt;/p&gt;

&lt;p&gt;The cold start is subsecond. I've never noticed it in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS App Runner
&lt;/h2&gt;

&lt;p&gt;I needed a place to run FastAPI. The options were Lambda, ECS, EC2, or App Runner.&lt;/p&gt;

&lt;p&gt;Lambda would have required restructuring the FastAPI app into a Lambda handler. &lt;br&gt;
Doable, but extra work for no gain at this stage.&lt;/p&gt;

&lt;p&gt;ECS gives you more control but requires more setup — load balancers, task definitions, service discovery. I didn't need that control yet.&lt;/p&gt;

&lt;p&gt;App Runner takes a container image from ECR and runs it. Auto-scaling, health checks, HTTPS, all handled. I push to ECR, App Runner deploys. That's it.&lt;/p&gt;

&lt;p&gt;For a solo founder building fast, App Runner is the right call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vercel + Next.js 14
&lt;/h2&gt;

&lt;p&gt;The frontend needed to be fast to build and fast to load on mobile. Inspectors use their phones in the field, sometimes with marginal 4G connections.&lt;/p&gt;

&lt;p&gt;Next.js App Router gives me Server Components for the initial data fetch (fast, no client-side waterfall) and Client Components for the real-time parts, the findings autosave, the photo upload progress, the observation condition buttons.&lt;/p&gt;

&lt;p&gt;The autosave debounces 800ms after the last keystroke. The inspector types a finding, stops typing, and it saves automatically. No save button. No lost data if they close the app.&lt;/p&gt;

&lt;p&gt;Vercel deploys on every push to main. Preview deployments for every PR. Zero infrastructure management on the frontend side.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS S3 for photos
&lt;/h2&gt;

&lt;p&gt;Home inspectors take a lot of photos. Every defect gets documented. A full inspection might have 20-40 photos.&lt;/p&gt;

&lt;p&gt;The naive approach: upload photos to the FastAPI backend, which forwards them to S3. That works, but it doubles the bandwidth and adds latency, the photo travels from the phone to App Runner to S3 instead of phone to S3 directly.&lt;/p&gt;

&lt;p&gt;The right approach: presigned PUT URLs. The backend generates a temporary URL that lets the phone upload directly to S3. The file never touches the backend. &lt;br&gt;
The path is:&lt;br&gt;
&lt;code&gt;tenants/{tenant_id}/inspections/{inspection_id}/findings/{finding_id}/{uuid}.jpg&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Tenant isolation in the S3 key structure. The backend only stores the key and a presigned GET URL (24h expiry) for serving the photo in the UI and PDF.&lt;/p&gt;

&lt;h2&gt;
  
  
  WeasyPrint for PDF generation
&lt;/h2&gt;

&lt;p&gt;Generating PDFs is harder than it looks. The options I considered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Headless Chrome (Puppeteer):&lt;/strong&gt; Works great, but adds a Chrome binary to the Docker image. That's 300MB+ and a security surface I didn't want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ReportLab:&lt;/strong&gt; Python library, but building complex layouts with photos and branding in ReportLab feels like writing assembly code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WeasyPrint:&lt;/strong&gt; Renders HTML/CSS to PDF server-side. I write the report as 
a Jinja2 HTML template, WeasyPrint converts it. The template is readable, maintainable, and easy to update.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WeasyPrint has quirks — it doesn't support flexbox in table cells, and &lt;code&gt;object-fit&lt;/code&gt; doesn't work. But once you know the constraints, it's predictable. A 15-section inspection report with photos renders in under 3 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Cognito
&lt;/h2&gt;

&lt;p&gt;Auth is not where I wanted to spend time. Cognito handles JWT issuance, token refresh, user management, and MFA if I ever need it. I store a custom &lt;code&gt;tenant_id&lt;/code&gt; claim in the token so the backend knows which tenant is making the request without a database lookup on every call.&lt;/p&gt;

&lt;p&gt;The inspector's &lt;code&gt;inspector_id&lt;/code&gt; is resolved on the backend from the JWT &lt;code&gt;sub&lt;/code&gt; via a lookup in &lt;code&gt;inspector_profiles&lt;/code&gt;. The frontend never sends database IDs — only business data. This was an architecture decision I got right after initially getting it wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terraform for infrastructure
&lt;/h2&gt;

&lt;p&gt;Everything is in code. The Aurora cluster, App Runner service, S3 bucket, Cognito user pool, ECR repository, security groups, VPC, all Terraform in a separate &lt;code&gt;mcag-h0-infra&lt;/code&gt; repo.&lt;/p&gt;

&lt;p&gt;When something breaks, I know exactly what's deployed. When I need to &lt;br&gt;
replicate the environment, I run &lt;code&gt;terraform apply&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd change
&lt;/h2&gt;

&lt;p&gt;Honestly, not much at this stage. The stack is boring in the best possible way, proven technologies that do what they say.&lt;/p&gt;

&lt;p&gt;The one thing I'd add sooner: CloudFront in front of S3. Right now I'm using presigned GET URLs with 24h expiry for photos in the PDF. That creates a race condition if the PDF is generated close to the URL expiry. CloudFront with Origin Access Control gives permanent URLs that WeasyPrint can always reach.&lt;/p&gt;

&lt;p&gt;That's on the roadmap for July.&lt;/p&gt;

&lt;h2&gt;
  
  
  The live product
&lt;/h2&gt;

&lt;p&gt;InspectIQ is running in production at &lt;a href="https://mcag-h0.vercel.app" rel="noopener noreferrer"&gt;mcag-h0.vercel.app&lt;/a&gt;. &lt;br&gt;
First customer confirmed at $99/month starting July 2026.&lt;/p&gt;

&lt;p&gt;The stack made it possible to build a production-grade multi-tenant SaaS in the H0 Hackathon window. Not a demo. A product.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I created this content for the purposes of entering the H0: Hack the Zero Stack Hackathon by AWS and Vercel. #H0Hackathon&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hackathon</category>
      <category>aws</category>
      <category>postgres</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>From hackathon to real customer: building InspectIQ for Florida home inspectors</title>
      <dc:creator>Carolina</dc:creator>
      <pubDate>Fri, 26 Jun 2026 15:27:21 +0000</pubDate>
      <link>https://dev.to/alomarac3/from-hackathon-to-real-customer-building-inspectiq-for-florida-home-inspectors-fa5</link>
      <guid>https://dev.to/alomarac3/from-hackathon-to-real-customer-building-inspectiq-for-florida-home-inspectors-fa5</guid>
      <description>&lt;p&gt;Most hackathon projects don't have a customer before they're finished.&lt;/p&gt;

&lt;p&gt;InspectIQ does.&lt;/p&gt;

&lt;p&gt;Before I wrote a single line of code, I had a conversation with REBS Property Specialist LLC — a licensed Florida home inspector with 30 years of experience. &lt;br&gt;
I asked him one question: what's the most painful part of your job that software hasn't fixed?&lt;/p&gt;

&lt;p&gt;His answer was immediate: the report.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem nobody talks about
&lt;/h2&gt;

&lt;p&gt;Florida has over 8,000 licensed home inspectors. After every inspection, which covers 12 sections, dozens of condition items, and photos of every defect, the inspector has to produce a professional PDF report and deliver it to the client, often within hours.&lt;/p&gt;

&lt;p&gt;Most inspectors are still doing this with paper forms, clipboards, and software that was built before smartphones existed. The apps that do exist weren't designed for mobile. They weren't designed for field use. They weren't designed for someone who needs to capture 40 findings, take 20 photos, and generate a branded PDF while standing in someone's attic in July in Florida.&lt;/p&gt;

&lt;p&gt;That's the problem InspectIQ solves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;InspectIQ is a mobile-first SaaS platform that covers the full inspection workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;In the field:&lt;/strong&gt; the inspector opens the app on their phone, selects a section (Roof, Electrical, Plumbing, etc.), and starts documenting. Every finding has a condition rating (GOOD, MARGINAL, DEFECTIVE), free-form observations, and photos that upload directly from the phone to S3 — no waiting for a slow backend upload.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Component observations:&lt;/strong&gt; beyond free-form findings, each section has structured metadata fields (what material is the roof? what type of electrical panel?) and condition items that map directly to the InterNACHI inspection standard Florida inspectors are trained on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The report:&lt;/strong&gt; one click generates a professional PDF with the inspector's branding — their logo, their colors, their license numbers — plus all the findings, conditions, photos, and section disclaimers. WeasyPrint renders it server-side in under 3 seconds.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The distribution insight
&lt;/h2&gt;

&lt;p&gt;Here's what made me confident this could work as a real business:&lt;/p&gt;

&lt;p&gt;REBS runs an inspection school. Every year, 30 to 60 new inspectors graduate from his program and enter the Florida market. They need software from day one of their career. If InspectIQ is the tool they learn on, the CAC for those customers is essentially zero, they come through the school channel already trained on the product.&lt;/p&gt;

&lt;p&gt;That's not a hackathon pitch. That's a real distribution channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building it during H0
&lt;/h2&gt;

&lt;p&gt;The H0 Hackathon gave me the infrastructure constraint I needed: Aurora PostgreSQL on AWS with a Vercel frontend. That's exactly the stack I wanted for production anyway.&lt;/p&gt;

&lt;p&gt;So instead of building a demo, I built the real thing. Multi-tenant database with Row Level Security. White-label branding per inspector. A 5-state inspection lifecycle with write-locks on delivered reports. S3 photo storage with presigned URLs. Thirteen database migrations.&lt;/p&gt;

&lt;p&gt;It's not a prototype. It's a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stands today
&lt;/h2&gt;

&lt;p&gt;InspectIQ is live at &lt;a href="https://mcag-h0.vercel.app" rel="noopener noreferrer"&gt;mcag-h0.vercel.app&lt;/a&gt;. REBS confirmed at $99/month starting July 2026. His network of 3-5 inspectors are next in the pipeline.&lt;/p&gt;

&lt;p&gt;The MRR targets: $500 in month 1, $5,000 by month 6, $30,000 by month 18.&lt;/p&gt;

&lt;p&gt;The path to get there runs through every inspector school in Florida.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I created this content for the purposes of entering the H0: Hack the Zero Stack &lt;br&gt;
Hackathon by AWS and Vercel. #H0Hackathon&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hackathon</category>
      <category>saas</category>
      <category>startup</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How I built multi-tenant Row Level Security with Aurora PostgreSQL for a B2B SaaS — H0 Hackathon</title>
      <dc:creator>Carolina</dc:creator>
      <pubDate>Fri, 26 Jun 2026 15:21:53 +0000</pubDate>
      <link>https://dev.to/alomarac3/how-i-built-multi-tenant-row-level-security-with-aurora-postgresql-for-a-b2b-saas-h0-hackathon-1m54</link>
      <guid>https://dev.to/alomarac3/how-i-built-multi-tenant-row-level-security-with-aurora-postgresql-for-a-b2b-saas-h0-hackathon-1m54</guid>
      <description>&lt;p&gt;I'll be honest: I almost did multi-tenancy the wrong way.&lt;/p&gt;

&lt;p&gt;When I started building InspectIQ  "a SaaS platform for Florida home inspectors" my first instinct was to add a &lt;code&gt;tenant_id&lt;/code&gt; column to every table and filter it in the application layer. Every query would have a &lt;code&gt;WHERE tenant_id = :current_tenant&lt;/code&gt; clause. Simple, familiar, done.&lt;/p&gt;

&lt;p&gt;Then I thought about what happens when you forget one.&lt;/p&gt;

&lt;p&gt;One missing WHERE clause. One endpoint that skips the filter. One inspector sees another inspector's client data. In a home inspection business, that's not just a bug — it's a HIPAA-adjacent nightmare and a trust-destroying moment with your first customer.&lt;/p&gt;

&lt;p&gt;So I did it properly from day one: Row Level Security at the database layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Row Level Security?
&lt;/h2&gt;

&lt;p&gt;RLS is a PostgreSQL feature that lets you define policies directly on tables. &lt;br&gt;
When a user queries a table, the policy runs automatically, before your application code even sees the results. You can't forget to apply it. You can't bypass it with a careless JOIN. It's enforced at the lowest possible layer.&lt;/p&gt;

&lt;p&gt;For a multi-tenant SaaS, this is exactly what you want.&lt;/p&gt;
&lt;h2&gt;
  
  
  How I implemented it
&lt;/h2&gt;

&lt;p&gt;Every table in InspectIQ has this pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;inspections&lt;/span&gt; &lt;span class="n"&gt;ENABLE&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;inspections&lt;/span&gt; &lt;span class="k"&gt;FORCE&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="k"&gt;LEVEL&lt;/span&gt; &lt;span class="k"&gt;SECURITY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;POLICY&lt;/span&gt; &lt;span class="n"&gt;tenant_isolation&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;inspections&lt;/span&gt;
  &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_setting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app.current_tenant_id'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;FORCE&lt;/code&gt; is important — it applies the policy even to the table owner. &lt;br&gt;
No superuser backdoor.&lt;/p&gt;

&lt;p&gt;The tenant context comes from the JWT. When an inspector logs in, their&lt;code&gt;tenant_id&lt;/code&gt; is embedded as a custom Cognito claim. The FastAPI middleware extracts it and sets it at the start of every request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SET LOCAL app.current_tenant_id = &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SET LOCAL&lt;/code&gt; scopes the setting to the current transaction. When the transaction ends, it's gone. No leakage between requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aurora PostgreSQL Serverless v2
&lt;/h2&gt;

&lt;p&gt;I'm running this on Aurora PostgreSQL Serverless v2 on AWS. For an early-stage SaaS, the economics are compelling: you pay for what you use, it scales to zero between requests, and you get full PostgreSQL compatibility.&lt;/p&gt;

&lt;p&gt;The RLS policies work identically on Aurora as they do on vanilla PostgreSQL. &lt;br&gt;
No Aurora-specific gotchas, no driver changes.&lt;/p&gt;

&lt;p&gt;One thing I learned the hard way: Aurora with restricted database users (which is the right security posture) means your migration user needs DDL privileges separately from your application user. I run schema migrations via a bastion EC2 through AWS SSM — no SSH, no exposed credentials, no public IP on the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment it clicked
&lt;/h2&gt;

&lt;p&gt;The first time I tested with two tenants, I logged in as Tenant A and ran a query that should only return Tenant B's data. I got an empty result set.&lt;/p&gt;

&lt;p&gt;Not an error. Not "access denied." Just nothing — because from the database's perspective, those rows don't exist for this tenant.&lt;/p&gt;

&lt;p&gt;That's the right behavior. That's what you want. And I didn't have to write a single WHERE clause in my application code to get it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tradeoff
&lt;/h2&gt;

&lt;p&gt;RLS adds complexity to your migration process and your database user setup. &lt;br&gt;
You need to think carefully about which user runs DDL vs. which user runs application queries. You need to test that your policies actually work, both that they block the right data AND that they return the right data for the correct tenant.&lt;/p&gt;

&lt;p&gt;But for a B2B SaaS where tenant isolation is a hard requirement, the tradeoff is worth it. The alternative — application-level filtering — is a security debt that compounds with every new endpoint you add.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;InspectIQ is live at &lt;a href="https://mcag-h0.vercel.app" rel="noopener noreferrer"&gt;mcag-h0.vercel.app&lt;/a&gt; with a confirmed first customer (REBS Property Specialist LLC, a 30-year Florida licensed inspector) starting July 2026.&lt;/p&gt;

&lt;p&gt;If you're building multi-tenant SaaS on Aurora PostgreSQL and want to talk architecture, find me here on dev.to.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I created this content for the purposes of entering the H0: Hack the Zero Stack &lt;br&gt;
Hackathon by AWS and Vercel. #H0Hackathon&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hackathon</category>
      <category>aws</category>
      <category>postgres</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
