<?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: Haseeb Sheikh</title>
    <description>The latest articles on DEV Community by Haseeb Sheikh (@haseeb_sheikh_0f627e74ba7).</description>
    <link>https://dev.to/haseeb_sheikh_0f627e74ba7</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%2F4069738%2F47cc3978-648d-4861-a758-9b3ae94f0113.jpg</url>
      <title>DEV Community: Haseeb Sheikh</title>
      <link>https://dev.to/haseeb_sheikh_0f627e74ba7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/haseeb_sheikh_0f627e74ba7"/>
    <language>en</language>
    <item>
      <title>How to Evaluate a SaaS Developer: 8 Technical Checks</title>
      <dc:creator>Haseeb Sheikh</dc:creator>
      <pubDate>Sun, 20 Sep 2026 13:43:47 +0000</pubDate>
      <link>https://dev.to/haseeb_sheikh_0f627e74ba7/how-to-evaluate-a-saas-developer-8-technical-checks-ljc</link>
      <guid>https://dev.to/haseeb_sheikh_0f627e74ba7/how-to-evaluate-a-saas-developer-8-technical-checks-ljc</guid>
      <description>&lt;p&gt;A SaaS application can look simple from the outside while having a surprisingly complicated system underneath.&lt;/p&gt;

&lt;p&gt;A signup form might involve authentication, database transactions, email verification, permissions, subscriptions, and background jobs. A "team" feature might require organizations, memberships, roles, invitations, and authorization checks across every API endpoint.&lt;/p&gt;

&lt;p&gt;That's why evaluating a SaaS developer shouldn't stop at their portfolio or hourly rate.&lt;/p&gt;

&lt;p&gt;Before hiring someone, you want to understand &lt;strong&gt;how they think about the parts of the system that become difficult to change later&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here are eight technical areas worth discussing.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Ask Them to Draw the Architecture
&lt;/h2&gt;

&lt;p&gt;Don't start with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Which framework will you use?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Start with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How would you structure this application?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, a relatively simple SaaS might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser / Mobile App
        |
        v
     API Layer
        |
        v
  Business Logic
    /    |     \
   v     v      v
Postgres Stripe  Queue
           |
           v
        Webhooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact architecture will depend on the product.&lt;/p&gt;

&lt;p&gt;The important part is whether the developer can explain &lt;strong&gt;why&lt;/strong&gt; each component exists.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why is PostgreSQL being used?&lt;/li&gt;
&lt;li&gt;Where does authorization happen?&lt;/li&gt;
&lt;li&gt;How are background jobs handled?&lt;/li&gt;
&lt;li&gt;Where is payment state stored?&lt;/li&gt;
&lt;li&gt;How are webhook events processed?&lt;/li&gt;
&lt;li&gt;What happens when an external service is unavailable?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A developer who can explain trade-offs is more useful than someone who can simply list technologies.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Look at the Database Design
&lt;/h2&gt;

&lt;p&gt;A SaaS database usually contains relationships that become more important as the product grows.&lt;/p&gt;

&lt;p&gt;For example:&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;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;organizations&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;memberships&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;organization_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;organizations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

  &lt;span class="k"&gt;role&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;organization_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&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;This is more useful than simply creating a &lt;code&gt;users&lt;/code&gt; table with an &lt;code&gt;organization_id&lt;/code&gt; column and assuming the requirements will never change.&lt;/p&gt;

&lt;p&gt;Ask the developer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are the core entities?&lt;/li&gt;
&lt;li&gt;How are relationships represented?&lt;/li&gt;
&lt;li&gt;Where are foreign keys used?&lt;/li&gt;
&lt;li&gt;Which fields need indexes?&lt;/li&gt;
&lt;li&gt;How will schema changes be migrated?&lt;/li&gt;
&lt;li&gt;What happens if an organization is deleted?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need to know SQL deeply.&lt;/p&gt;

&lt;p&gt;You want to see whether the developer is thinking about &lt;strong&gt;data integrity and future changes&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Check How Authorization Works
&lt;/h2&gt;

&lt;p&gt;Authentication answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Who is this user?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Authorization answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What is this user allowed to do?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those are different problems.&lt;/p&gt;

&lt;p&gt;For example, this is not enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/projects/:id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requireAuth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getProject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user may be authenticated, but do they actually belong to the organization that owns the project?&lt;/p&gt;

&lt;p&gt;A simplified authorization check might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canAccessProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`
    SELECT 1
    FROM projects p
    JOIN memberships m
      ON m.organization_id = p.organization_id
    WHERE p.id = $1
      AND m.user_id = $2
    `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;projectId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rowCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;The important question for a SaaS developer is not whether they know this exact implementation.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do you prevent one organization's users from accessing another organization's data?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If they haven't thought about tenant isolation, that's worth investigating before development starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Ask How They Handle Database Migrations
&lt;/h2&gt;

&lt;p&gt;A production database should not depend on manually editing tables.&lt;/p&gt;

&lt;p&gt;Suppose version one has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;users
projects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six months later you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;organizations
memberships
projects
subscriptions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How does the developer introduce those changes?&lt;/p&gt;

&lt;p&gt;A migration system lets schema changes become repeatable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;001_create_users
002_create_projects
003_create_organizations
004_create_memberships
005_add_subscription_status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters when another developer joins the project or when you deploy to staging and production.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"If I clone the repository and connect a fresh database, can I reproduce the current schema?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A good development workflow should make the answer close to "yes."&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Payments Are More Than a Checkout Button
&lt;/h2&gt;

&lt;p&gt;Payment integrations are a good way to distinguish a demo from a production system.&lt;/p&gt;

&lt;p&gt;For example, don't make the browser the source of truth for subscription status.&lt;/p&gt;

&lt;p&gt;A typical flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer
   |
   v
Payment Provider
   |
   v
Webhook
   |
   v
Your API
   |
   v
Database
   |
   v
Account Access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One common mistake is processing the same webhook event twice.&lt;/p&gt;

&lt;p&gt;External services can retry webhook deliveries, so your handler should be designed to be idempotent.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/stripe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;constructStripeEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT 1 FROM webhook_events WHERE event_id = $1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rowCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`
    INSERT INTO webhook_events (event_id, event_type)
    VALUES ($1, $2)
    `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invoice.paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;handleSuccessfulPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&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;In a real implementation, the event insertion and business update should also be designed carefully around database transactions and failure scenarios.&lt;/p&gt;

&lt;p&gt;Ask the developer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What happens if Stripe sends the same webhook twice?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's a simple question that can reveal a lot about their production experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Ask What Happens When Requirements Change
&lt;/h2&gt;

&lt;p&gt;This is one of the most useful technical discussions before starting a SaaS project.&lt;/p&gt;

&lt;p&gt;Suppose the original requirement is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One user → one project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the business changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Organization
  ├── many users
  └── many projects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can the existing architecture accommodate this?&lt;/p&gt;

&lt;p&gt;You don't want a developer to predict every future requirement.&lt;/p&gt;

&lt;p&gt;That's impossible.&lt;/p&gt;

&lt;p&gt;Instead, look for code and architecture that keep important responsibilities reasonably separated.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controller
   ↓
Service
   ↓
Repository / Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;doesn't automatically make an application good, but it can make certain changes easier than putting database queries, business rules, authentication, and HTTP handling into one enormous function.&lt;/p&gt;

&lt;p&gt;The question isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Will you build a perfect architecture?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can the architecture evolve without rewriting unrelated parts of the system?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  7. Ask How They Test the Important Parts
&lt;/h2&gt;

&lt;p&gt;You don't necessarily need 100% test coverage.&lt;/p&gt;

&lt;p&gt;You do need confidence around the parts where bugs can cause serious problems.&lt;/p&gt;

&lt;p&gt;For a SaaS application, that could include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authentication
Authorization
Billing
Subscription state
Critical business rules
Data access
Webhook processing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user cannot access another organization's project&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/projects/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;otherOrganizationsProjectId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&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;This kind of test is more valuable than simply testing whether a button renders.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Which parts of the application would you test first?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer should reveal whether the developer understands where the application's actual risk lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Ask What Happens After Deployment
&lt;/h2&gt;

&lt;p&gt;Deployment isn't the end of the engineering work.&lt;/p&gt;

&lt;p&gt;Ask what happens when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a database migration fails&lt;/li&gt;
&lt;li&gt;an API starts returning errors&lt;/li&gt;
&lt;li&gt;a background job gets stuck&lt;/li&gt;
&lt;li&gt;a deployment introduces a bug&lt;/li&gt;
&lt;li&gt;an external API goes down&lt;/li&gt;
&lt;li&gt;a production database needs to be restored&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At minimum, you should discuss:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Git repository
Environment variables
Database backups
Migrations
Logging
Error monitoring
Deployment process
Rollback strategy
Access management
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also make sure the project isn't dependent on one person's laptop or personal accounts.&lt;/p&gt;

&lt;p&gt;The repository, cloud infrastructure, database, domain, payment account, and important service accounts should have a clear ownership model.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Technical Interview for a SaaS Developer
&lt;/h2&gt;

&lt;p&gt;You don't need a three-hour interview.&lt;/p&gt;

&lt;p&gt;Give the developer a short description of your product and ask these questions:&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;How would you structure this application?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Database
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;What would the main entities and relationships look like?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Authorization
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;How would you prevent users from accessing another customer's data?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Payments
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;What happens if a payment succeeds but your webhook isn't processed?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Scaling
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Which part of this system do you expect to become a bottleneck first?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Changes
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;What part of this design would be hardest to change later?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Deployment
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;How would you safely deploy database changes?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Handoff
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;If another developer takes over six months from now, what would they need?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You don't have to know the perfect answers.&lt;/p&gt;

&lt;p&gt;Pay attention to whether the developer identifies assumptions, asks clarifying questions, discusses trade-offs, and explains failure cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Biggest Red Flag Isn't a Specific Technology
&lt;/h2&gt;

&lt;p&gt;A developer using MongoDB isn't automatically bad.&lt;/p&gt;

&lt;p&gt;A developer using PostgreSQL isn't automatically good.&lt;/p&gt;

&lt;p&gt;The same applies to React, Next.js, Node.js, AWS, Docker, serverless functions, or any other technology.&lt;/p&gt;

&lt;p&gt;The more useful signal is whether the developer can connect technical decisions to actual requirements.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requirement
    ↓
Business Rule
    ↓
Data Model
    ↓
API / Service
    ↓
Authorization
    ↓
Testing
    ↓
Deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That chain is what turns a product requirement into an actual system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Checklist
&lt;/h2&gt;

&lt;p&gt;Before hiring a developer for a SaaS project, make sure you can get clear answers to these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Can they explain the proposed architecture?&lt;/li&gt;
&lt;li&gt;[ ] Can they explain the database relationships?&lt;/li&gt;
&lt;li&gt;[ ] Do they understand authentication vs authorization?&lt;/li&gt;
&lt;li&gt;[ ] Do they have a migration strategy?&lt;/li&gt;
&lt;li&gt;[ ] Do they understand payment/webhook failure cases?&lt;/li&gt;
&lt;li&gt;[ ] Can the system accommodate changing requirements?&lt;/li&gt;
&lt;li&gt;[ ] Are important business rules tested?&lt;/li&gt;
&lt;li&gt;[ ] Is deployment reproducible?&lt;/li&gt;
&lt;li&gt;[ ] Are backups and monitoring considered?&lt;/li&gt;
&lt;li&gt;[ ] Can another developer take over?&lt;/li&gt;
&lt;li&gt;[ ] Does the company own the critical accounts and infrastructure?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn't to find a developer who predicts every future problem.&lt;/p&gt;

&lt;p&gt;That's impossible.&lt;/p&gt;

&lt;p&gt;The goal is to find someone who recognizes the important engineering decisions early, understands the trade-offs, and doesn't treat production software as nothing more than a collection of screens and API endpoints.&lt;/p&gt;

&lt;p&gt;I'm Haseeb, building SaaS products at Seebify.&lt;/p&gt;

&lt;p&gt;Full write-up with the business-side reasoning → &lt;a href="https://www.seebify.com/blog/how-to-choose-a-saas-developer" rel="noopener noreferrer"&gt;https://www.seebify.com/blog/how-to-choose-a-saas-developer&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>saas</category>
      <category>postgres</category>
      <category>architecture</category>
    </item>
    <item>
      <title>How Much Does It Actually Cost to Build a SaaS in 2026? A Technical Breakdown</title>
      <dc:creator>Haseeb Sheikh</dc:creator>
      <pubDate>Sun, 13 Sep 2026 10:35:45 +0000</pubDate>
      <link>https://dev.to/haseeb_sheikh_0f627e74ba7/how-much-does-it-actually-cost-to-build-a-saas-in-2026-a-technical-breakdown-6hd</link>
      <guid>https://dev.to/haseeb_sheikh_0f627e74ba7/how-much-does-it-actually-cost-to-build-a-saas-in-2026-a-technical-breakdown-6hd</guid>
      <description>&lt;p&gt;A SaaS application can look simple from the outside and still require significant backend engineering.&lt;/p&gt;

&lt;p&gt;A dashboard with ten screens might be cheap to build. A dashboard with multi-tenant data, role-based permissions, Stripe subscriptions, webhooks, integrations, background jobs, and production monitoring is a completely different engineering problem.&lt;/p&gt;

&lt;p&gt;So instead of estimating SaaS development cost by the number of pages or screens, let's look at what actually creates the engineering work.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical SaaS Cost Breakdown
&lt;/h2&gt;

&lt;p&gt;For a typical web SaaS product, these are useful planning ranges:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product stage&lt;/th&gt;
&lt;th&gt;Typical budget&lt;/th&gt;
&lt;th&gt;Typical timeline&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prototype / validation&lt;/td&gt;
&lt;td&gt;$500–$2,500&lt;/td&gt;
&lt;td&gt;1–3 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lean SaaS MVP&lt;/td&gt;
&lt;td&gt;$3,000–$8,000&lt;/td&gt;
&lt;td&gt;4–8 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production-ready SaaS&lt;/td&gt;
&lt;td&gt;$7,000–$20,000&lt;/td&gt;
&lt;td&gt;6–12 weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex SaaS&lt;/td&gt;
&lt;td&gt;$20,000–$50,000+&lt;/td&gt;
&lt;td&gt;3–6+ months&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These aren't fixed market prices. The actual number depends heavily on the architecture and workflows your product requires.&lt;/p&gt;

&lt;p&gt;The important question isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How much does SaaS development cost?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What engineering does this particular SaaS need to work reliably for real users?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's break that down.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Your Database Architecture Matters More Than Your Number of Screens
&lt;/h2&gt;

&lt;p&gt;A common mistake is estimating a SaaS based on UI screens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login&lt;/li&gt;
&lt;li&gt;Dashboard&lt;/li&gt;
&lt;li&gt;Settings&lt;/li&gt;
&lt;li&gt;Billing&lt;/li&gt;
&lt;li&gt;Admin&lt;/li&gt;
&lt;li&gt;Reports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the real complexity is usually underneath those screens.&lt;/p&gt;

&lt;p&gt;For example, a B2B SaaS might have a structure like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Organization
    ↓
Members
    ↓
Users
    ↓
Projects
    ↓
Transactions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every important query needs to understand which organization owns the data.&lt;/p&gt;

&lt;p&gt;A simplified PostgreSQL query might look like this:&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;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;projects&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;organization_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;organization_id&lt;/code&gt; isn't just another column.&lt;/p&gt;

&lt;p&gt;It is part of your tenant-isolation strategy.&lt;/p&gt;

&lt;p&gt;If tenant isolation is implemented incorrectly, one customer could potentially access another customer's data.&lt;/p&gt;

&lt;p&gt;That's why database architecture becomes a major part of SaaS development cost as the product becomes more complex.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Authentication Gets Expensive When Permissions Get Real
&lt;/h2&gt;

&lt;p&gt;A basic application might only need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User → Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A production B2B SaaS can look more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  ↓
Organization
  ↓
Team
  ↓
Role
  ↓
Permission
  ↓
Resource
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suddenly authentication isn't just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;dashboard&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;You may need to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sessions&lt;/li&gt;
&lt;li&gt;Password resets&lt;/li&gt;
&lt;li&gt;Email verification&lt;/li&gt;
&lt;li&gt;Organization membership&lt;/li&gt;
&lt;li&gt;Roles&lt;/li&gt;
&lt;li&gt;Permissions&lt;/li&gt;
&lt;li&gt;Resource-level authorization&lt;/li&gt;
&lt;li&gt;Tenant isolation&lt;/li&gt;
&lt;li&gt;Admin access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canEditProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;organizationId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;organizationId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;project:edit&lt;/span&gt;&lt;span class="dl"&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;The code itself is small.&lt;/p&gt;

&lt;p&gt;The expensive part is making sure these rules are applied consistently across every API endpoint and every important operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Stripe Is More Than a Checkout Button
&lt;/h2&gt;

&lt;p&gt;One of the easiest SaaS features to underestimate is billing.&lt;/p&gt;

&lt;p&gt;A simple requirement might be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We just need Stripe subscriptions."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But production billing usually involves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Checkout
   ↓
Stripe
   ↓
Webhook
   ↓
Subscription state
   ↓
Database
   ↓
User access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simplified subscription creation might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;priceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&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;That is only the beginning.&lt;/p&gt;

&lt;p&gt;You also need to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Successful payments&lt;/li&gt;
&lt;li&gt;Failed payments&lt;/li&gt;
&lt;li&gt;Cancelled subscriptions&lt;/li&gt;
&lt;li&gt;Upgrades&lt;/li&gt;
&lt;li&gt;Downgrades&lt;/li&gt;
&lt;li&gt;Refunds&lt;/li&gt;
&lt;li&gt;Proration&lt;/li&gt;
&lt;li&gt;Duplicate webhook events&lt;/li&gt;
&lt;li&gt;Expired cards&lt;/li&gt;
&lt;li&gt;Subscription status&lt;/li&gt;
&lt;li&gt;Access after cancellation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One particularly important rule is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't treat the browser's checkout response as the source of truth for subscription state.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Stripe webhooks should update your backend when important billing events occur.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/stripe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constructEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stripe-signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRIPE_WEBHOOK_SECRET&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customer.subscription.updated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;The mechanism is a webhook-driven state update.&lt;/p&gt;

&lt;p&gt;The engineering cost comes from making that state reliable when events arrive late, twice, or in an unexpected order.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Integrations Multiply the Number of Failure Points
&lt;/h2&gt;

&lt;p&gt;Adding an integration can sound trivial:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We just need Google Calendar."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But an integration normally introduces another external system that your application doesn't control.&lt;/p&gt;

&lt;p&gt;You may need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your API
   ↓
OAuth
   ↓
External API
   ↓
Rate limits
   ↓
Retries
   ↓
External errors
   ↓
Data synchronization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, if your application creates calendar events, you need to decide what happens when the external API fails.&lt;/p&gt;

&lt;p&gt;A basic retry strategy might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createCalendarEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;calendar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;calendarId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createCalendarEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&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;In a real system, you'd usually want better retry policies, logging, idempotency, and background jobs.&lt;/p&gt;

&lt;p&gt;This is why "three integrations" can represent significantly more work than "three API calls."&lt;/p&gt;

&lt;h2&gt;
  
  
  5. AI Features Are Usually Workflows, Not API Calls
&lt;/h2&gt;

&lt;p&gt;AI can reduce development time for some parts of a product, but an AI feature is rarely just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A production AI workflow might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Request
     ↓
API
     ↓
Background Job
     ↓
AI Model
     ↓
Validation
     ↓
Database
     ↓
Notification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may need to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model failures&lt;/li&gt;
&lt;li&gt;Timeouts&lt;/li&gt;
&lt;li&gt;Token usage&lt;/li&gt;
&lt;li&gt;Long-running requests&lt;/li&gt;
&lt;li&gt;Background processing&lt;/li&gt;
&lt;li&gt;Invalid model output&lt;/li&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Rate limits&lt;/li&gt;
&lt;li&gt;Cost controls&lt;/li&gt;
&lt;li&gt;Human approval&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The expensive part is often everything surrounding the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Production Readiness Is Where "Cheap" MVPs Become Expensive
&lt;/h2&gt;

&lt;p&gt;A demo can work without many production systems.&lt;/p&gt;

&lt;p&gt;A real SaaS shouldn't depend on luck.&lt;/p&gt;

&lt;p&gt;Production readiness can include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Automated testing
        +
Secure secrets
        +
Database backups
        +
Error tracking
        +
Monitoring
        +
Rate limiting
        +
CI/CD
        +
Staging
        +
Database migrations
        +
Rollback strategy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, an application might work perfectly during development.&lt;/p&gt;

&lt;p&gt;Then a production database migration fails.&lt;/p&gt;

&lt;p&gt;Or a webhook starts sending duplicate events.&lt;/p&gt;

&lt;p&gt;Or one API endpoint receives unexpected traffic.&lt;/p&gt;

&lt;p&gt;Or a deployment introduces a regression.&lt;/p&gt;

&lt;p&gt;Production engineering exists to make those situations manageable.&lt;/p&gt;

&lt;p&gt;That's why a production-ready SaaS can cost substantially more than a prototype with the same UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. A Lean MVP Should Reduce Scope, Not Engineering Quality
&lt;/h2&gt;

&lt;p&gt;This is one of the most important distinctions when budgeting a SaaS.&lt;/p&gt;

&lt;p&gt;Suppose your complete product vision contains 30 features.&lt;/p&gt;

&lt;p&gt;You might only need five to validate whether customers will actually pay.&lt;/p&gt;

&lt;p&gt;Build those five.&lt;/p&gt;

&lt;p&gt;But don't intentionally build them badly.&lt;/p&gt;

&lt;p&gt;A focused MVP could still have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A proper PostgreSQL schema&lt;/li&gt;
&lt;li&gt;Secure authentication&lt;/li&gt;
&lt;li&gt;Clean API boundaries&lt;/li&gt;
&lt;li&gt;Tenant isolation&lt;/li&gt;
&lt;li&gt;Tested critical workflows&lt;/li&gt;
&lt;li&gt;Stripe integration&lt;/li&gt;
&lt;li&gt;Error monitoring&lt;/li&gt;
&lt;li&gt;Production deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You're reducing &lt;strong&gt;what you build&lt;/strong&gt;, not deliberately reducing &lt;strong&gt;how reliably you build it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is often the best way to reduce the initial SaaS budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  So Where Does the Money Actually Go?
&lt;/h2&gt;

&lt;p&gt;Instead of estimating a SaaS using one number, break the project into engineering categories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Product discovery
        ↓
Architecture &amp;amp; database
        ↓
Frontend + backend
        ↓
Authentication &amp;amp; permissions
        ↓
Billing &amp;amp; integrations
        ↓
Testing &amp;amp; production readiness
        ↓
Infrastructure &amp;amp; deployment
        ↓
Post-launch engineering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, a focused SaaS with authentication, a dashboard, one core workflow, PostgreSQL, an admin panel, Stripe, and production deployment might fit into a relatively small MVP budget.&lt;/p&gt;

&lt;p&gt;Add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mobile applications&lt;/li&gt;
&lt;li&gt;Real-time chat&lt;/li&gt;
&lt;li&gt;Advanced analytics&lt;/li&gt;
&lt;li&gt;Multiple integrations&lt;/li&gt;
&lt;li&gt;Complex permissions&lt;/li&gt;
&lt;li&gt;AI workflows&lt;/li&gt;
&lt;li&gt;International billing&lt;/li&gt;
&lt;li&gt;Advanced reporting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…and the engineering scope can increase dramatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About Infrastructure Costs?
&lt;/h2&gt;

&lt;p&gt;Development cost and infrastructure cost are two different budgets.&lt;/p&gt;

&lt;p&gt;An early SaaS might use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;Vercel&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;Stripe&lt;/li&gt;
&lt;li&gt;GitHub&lt;/li&gt;
&lt;li&gt;An email provider&lt;/li&gt;
&lt;li&gt;Sentry or another monitoring platform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't necessarily need an expensive cloud architecture on day one.&lt;/p&gt;

&lt;p&gt;A small SaaS might spend tens or a few hundred dollars per month on infrastructure while spending thousands of dollars on the initial development.&lt;/p&gt;

&lt;p&gt;As usage increases, infrastructure costs can grow with traffic, database usage, storage, email, AI usage, and other services.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens After Launch?
&lt;/h2&gt;

&lt;p&gt;The development budget shouldn't end when version one goes live.&lt;/p&gt;

&lt;p&gt;Once real users arrive, you'll eventually deal with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bugs&lt;/li&gt;
&lt;li&gt;Performance issues&lt;/li&gt;
&lt;li&gt;Database optimisation&lt;/li&gt;
&lt;li&gt;New features&lt;/li&gt;
&lt;li&gt;Dependency updates&lt;/li&gt;
&lt;li&gt;Security improvements&lt;/li&gt;
&lt;li&gt;Infrastructure changes&lt;/li&gt;
&lt;li&gt;New integrations&lt;/li&gt;
&lt;li&gt;Refactoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why SaaS development should be treated as a lifecycle rather than a one-time project.&lt;/p&gt;

&lt;p&gt;A useful mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initial Build
     ↓
Real Users
     ↓
Feedback
     ↓
Iteration
     ↓
Scaling
     ↓
Architecture Improvements
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal isn't to predict every future requirement.&lt;/p&gt;

&lt;p&gt;It's to build the first version in a way that doesn't make every future change painful.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical SaaS Budget
&lt;/h2&gt;

&lt;p&gt;For planning purposes, these ranges are a reasonable starting point:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$500–$2,500&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prototype or early validation build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$3,000–$8,000&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Focused SaaS MVP with a narrow feature set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$7,000–$20,000&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Production-ready SaaS with stronger architecture, authentication, billing, testing, integrations, deployment, and monitoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$20,000–$50,000+&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Complex SaaS involving multiple roles, advanced workflows, significant integrations, AI systems, marketplaces, or mobile and web platforms.&lt;/p&gt;

&lt;p&gt;These numbers are planning ranges, not guarantees.&lt;/p&gt;

&lt;p&gt;The final cost should come from the &lt;strong&gt;workflows your product needs&lt;/strong&gt;, not the number of screens in the design.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Biggest SaaS Cost Mistake
&lt;/h2&gt;

&lt;p&gt;The most expensive mistake isn't always choosing the wrong technology.&lt;/p&gt;

&lt;p&gt;It's building too much before learning enough.&lt;/p&gt;

&lt;p&gt;You can spend months building advanced analytics, multiple dashboards, AI automation, mobile apps, and dozens of integrations — and then discover that customers only wanted one simple workflow.&lt;/p&gt;

&lt;p&gt;A better approach is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reduce unnecessary scope
        ↓
Launch earlier
        ↓
Get real feedback
        ↓
Measure usage
        ↓
Invest in what customers actually need
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AI-assisted development can make certain parts of this process faster, but it doesn't eliminate the need for architecture, security, database design, testing, code review, or production monitoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;There isn't one universal price for building a SaaS in 2026.&lt;/p&gt;

&lt;p&gt;A prototype might cost a few thousand dollars or less. A production-ready SaaS can require tens of thousands. A complex platform can go far beyond that.&lt;/p&gt;

&lt;p&gt;The better way to estimate the project is to ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What is the smallest useful version?&lt;/li&gt;
&lt;li&gt;What architecture does it need?&lt;/li&gt;
&lt;li&gt;Which workflows require serious backend engineering?&lt;/li&gt;
&lt;li&gt;What integrations and billing systems are involved?&lt;/li&gt;
&lt;li&gt;What does "production-ready" mean for this product?&lt;/li&gt;
&lt;li&gt;What can wait until real customers prove it is necessary?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Build less.&lt;/p&gt;

&lt;p&gt;But build the important parts properly.&lt;/p&gt;




&lt;p&gt;I'm Haseeb, building SaaS products at Seebify. I write about the engineering decisions behind building and scaling production-ready SaaS products.&lt;/p&gt;

&lt;p&gt;Full write-up with the business-side reasoning: &lt;a href="https://www.seebify.com/blog/how-much-does-it-cost-to-build-a-saas-in-2026?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;Seebify — How Much Does It Cost to Build a SaaS in 2026?&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sass</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>startup</category>
    </item>
    <item>
      <title>No Budget to Build Your SaaS? Here's How to Get Started Anyway</title>
      <dc:creator>Haseeb Sheikh</dc:creator>
      <pubDate>Sun, 30 Aug 2026 14:37:41 +0000</pubDate>
      <link>https://dev.to/haseeb_sheikh_0f627e74ba7/no-budget-to-build-your-saas-heres-how-to-get-started-anyway-1673</link>
      <guid>https://dev.to/haseeb_sheikh_0f627e74ba7/no-budget-to-build-your-saas-heres-how-to-get-started-anyway-1673</guid>
      <description>&lt;p&gt;No Budget to Build Your SaaS? Here's How to Get Started Anyway&lt;/p&gt;

&lt;p&gt;You have a SaaS idea.&lt;/p&gt;

&lt;p&gt;You believe it can solve a real problem.&lt;/p&gt;

&lt;p&gt;But there is one major problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You don't have the budget to hire a development team.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That doesn't necessarily mean you have to stop.&lt;/p&gt;

&lt;p&gt;You can start by building what you can, validating the idea, and preparing a clear opportunity for someone who may want to join you as a technical partner.&lt;/p&gt;

&lt;p&gt;The goal isn't to build an entire SaaS for free.&lt;/p&gt;

&lt;p&gt;The goal is to get far enough that the right person can look at what you've done and say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I understand what you're building. I understand the opportunity. I understand what you need from me."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That clarity can be extremely valuable when your cash budget is limited.&lt;/p&gt;




&lt;h1&gt;
  
  
  Your First Job Isn't Hiring a Developer
&lt;/h1&gt;

&lt;p&gt;One of the first mistakes I see founders make is immediately searching for a developer.&lt;/p&gt;

&lt;p&gt;They have an idea and send a message like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I have a SaaS idea. I don't have a budget. Can you build it for equity?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Put yourself in the developer's position.&lt;/p&gt;

&lt;p&gt;You know almost nothing about the opportunity.&lt;/p&gt;

&lt;p&gt;You don't know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What the product does&lt;/li&gt;
&lt;li&gt;Who needs it&lt;/li&gt;
&lt;li&gt;Whether anyone wants it&lt;/li&gt;
&lt;li&gt;What has already been built&lt;/li&gt;
&lt;li&gt;How much work is required&lt;/li&gt;
&lt;li&gt;What the founder is contributing&lt;/li&gt;
&lt;li&gt;What the equity actually means&lt;/li&gt;
&lt;li&gt;Where the company is going&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You're being asked to take a significant risk based almost entirely on trust.&lt;/p&gt;

&lt;p&gt;Instead, do some of the work first.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 1: Turn Your Idea Into a Clear Product
&lt;/h1&gt;

&lt;p&gt;Before you approach anyone, answer a few basic questions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What problem are you solving?
&lt;/h3&gt;

&lt;p&gt;Don't describe the technology.&lt;/p&gt;

&lt;p&gt;Describe the pain.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Small businesses spend several hours every week manually preparing reports from multiple systems."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's easier to understand than:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'm building an AI-powered business intelligence platform."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Who has this problem?
&lt;/h3&gt;

&lt;p&gt;Be specific.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Businesses"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Try:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Small agencies with 5–20 employees."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The more specific you are, the easier it becomes to validate your idea.&lt;/p&gt;

&lt;h3&gt;
  
  
  What does your product do?
&lt;/h3&gt;

&lt;p&gt;Explain the solution in simple language.&lt;/p&gt;

&lt;p&gt;Someone who knows nothing about your industry should understand it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why would someone pay for it?
&lt;/h3&gt;

&lt;p&gt;You don't need a perfect pricing strategy at this stage.&lt;/p&gt;

&lt;p&gt;But you should have a hypothesis.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"$49/month for small teams."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now you have something you can test.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 2: Build a Landing Page
&lt;/h1&gt;

&lt;p&gt;You don't need the complete application to start presenting your idea.&lt;/p&gt;

&lt;p&gt;Build a simple, professional landing page.&lt;/p&gt;

&lt;p&gt;It should explain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The problem&lt;/li&gt;
&lt;li&gt;Your solution&lt;/li&gt;
&lt;li&gt;Who it's for&lt;/li&gt;
&lt;li&gt;Main benefits&lt;/li&gt;
&lt;li&gt;How it works&lt;/li&gt;
&lt;li&gt;Early screenshots or mockups&lt;/li&gt;
&lt;li&gt;Call to action&lt;/li&gt;
&lt;li&gt;Contact or waitlist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This can often be done using website builders or AI-powered tools without hiring a developer.&lt;/p&gt;

&lt;p&gt;Depending on the tools you choose, you may be able to get the first version online for free or for a few dollars.&lt;/p&gt;

&lt;p&gt;The objective isn't to impress investors.&lt;/p&gt;

&lt;p&gt;It's to make your idea &lt;strong&gt;real enough to evaluate&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 3: Build a Basic Prototype With AI
&lt;/h1&gt;

&lt;p&gt;This is where things have changed dramatically for founders.&lt;/p&gt;

&lt;p&gt;You no longer necessarily need to hire a development team just to create the first visual version of a product.&lt;/p&gt;

&lt;p&gt;AI-powered development tools can help you create basic screens and workflows.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Signup&lt;/li&gt;
&lt;li&gt;Login&lt;/li&gt;
&lt;li&gt;Forgot password&lt;/li&gt;
&lt;li&gt;Dashboard&lt;/li&gt;
&lt;li&gt;Forms&lt;/li&gt;
&lt;li&gt;Settings&lt;/li&gt;
&lt;li&gt;Basic user workflow&lt;/li&gt;
&lt;li&gt;Simple admin screens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might be able to create an early prototype for free or with a small monthly AI-tool budget.&lt;/p&gt;

&lt;p&gt;But remember:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A prototype is not a production-ready SaaS.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's okay.&lt;/p&gt;

&lt;p&gt;You're not trying to build the final company yourself.&lt;/p&gt;

&lt;p&gt;You're creating enough of the product to demonstrate:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This is what I mean."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A clickable prototype can communicate more than ten pages of explanation.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 4: Talk to Potential Customers
&lt;/h1&gt;

&lt;p&gt;Don't spend months building before speaking to customers.&lt;/p&gt;

&lt;p&gt;Find people who match your target audience.&lt;/p&gt;

&lt;p&gt;Ask them about the problem.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do you solve this today?&lt;/p&gt;

&lt;p&gt;What is frustrating about your current process?&lt;/p&gt;

&lt;p&gt;How often does this problem happen?&lt;/p&gt;

&lt;p&gt;How much time does it take?&lt;/p&gt;

&lt;p&gt;Have you paid for a solution before?&lt;/p&gt;

&lt;p&gt;What would make you switch?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Don't focus only on whether they like your idea.&lt;/p&gt;

&lt;p&gt;People are very good at saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"That's a great idea."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That doesn't mean they'll use it.&lt;/p&gt;

&lt;p&gt;Look for stronger signals.&lt;/p&gt;

&lt;p&gt;Someone asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"When can I try it?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;is more interesting.&lt;/p&gt;

&lt;p&gt;Someone asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How much will it cost?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;is even more interesting.&lt;/p&gt;

&lt;p&gt;Someone willing to become a paying customer is stronger still.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 5: Package Everything Into a Simple Presentation
&lt;/h1&gt;

&lt;p&gt;This is probably the most important step if you have little or no budget.&lt;/p&gt;

&lt;p&gt;Before approaching a technical partner, create a simple presentation.&lt;/p&gt;

&lt;p&gt;It can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A PDF&lt;/li&gt;
&lt;li&gt;A Notion document&lt;/li&gt;
&lt;li&gt;Google Slides&lt;/li&gt;
&lt;li&gt;A short pitch deck&lt;/li&gt;
&lt;li&gt;A product brief&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It doesn't need to look like a Silicon Valley investor deck.&lt;/p&gt;

&lt;p&gt;It needs to be &lt;strong&gt;clear&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I would structure it like this:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Product
&lt;/h2&gt;

&lt;p&gt;What are you building?&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Problem
&lt;/h2&gt;

&lt;p&gt;What problem does it solve?&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Target customer
&lt;/h2&gt;

&lt;p&gt;Who will use it?&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Current solution
&lt;/h2&gt;

&lt;p&gt;How do people solve the problem today?&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Your solution
&lt;/h2&gt;

&lt;p&gt;How will your product improve the situation?&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Current progress
&lt;/h2&gt;

&lt;p&gt;What have you already done?&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Landing page completed&lt;br&gt;
Prototype completed&lt;br&gt;
25 customer interviews&lt;br&gt;
50 people interested&lt;br&gt;
5 beta users&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Whatever your numbers are, show them honestly.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Roadmap
&lt;/h2&gt;

&lt;p&gt;What do you want to build next?&lt;/p&gt;

&lt;p&gt;Keep the first version small.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. What you need
&lt;/h2&gt;

&lt;p&gt;Be extremely specific.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'm looking for a technical co-founder to help turn the current prototype into a production-ready SaaS."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's much better than:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I need a developer."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  9. What you can offer
&lt;/h2&gt;

&lt;p&gt;This is where you need to be completely transparent.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 6: Consider Equity + a Small Stipend
&lt;/h1&gt;

&lt;p&gt;If you genuinely don't have enough money to pay market-rate compensation, you have to acknowledge that.&lt;/p&gt;

&lt;p&gt;But that doesn't mean the only option is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Work for free."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can explore a structure that works for both sides.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Equity + small stipend&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The stipend provides some immediate compensation.&lt;/p&gt;

&lt;p&gt;The equity represents long-term upside and ownership.&lt;/p&gt;

&lt;p&gt;The exact arrangement depends entirely on the situation.&lt;/p&gt;

&lt;p&gt;There is no magic percentage that applies to every startup.&lt;/p&gt;

&lt;p&gt;A technical co-founder joining at the idea stage is very different from an experienced engineer joining an MVP that already has hundreds of customers.&lt;/p&gt;

&lt;p&gt;The arrangement should consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How early the company is&lt;/li&gt;
&lt;li&gt;How much work has already been done&lt;/li&gt;
&lt;li&gt;Expected time commitment&lt;/li&gt;
&lt;li&gt;Technical responsibility&lt;/li&gt;
&lt;li&gt;Existing traction&lt;/li&gt;
&lt;li&gt;Founder contributions&lt;/li&gt;
&lt;li&gt;Expected future commitment&lt;/li&gt;
&lt;li&gt;Whether the person is a co-founder or contractor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And whatever equity arrangement you make should be properly documented with appropriate legal advice.&lt;/p&gt;

&lt;p&gt;The most important thing is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't hide the risk. Explain it.&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 7: Don't Sell Equity. Sell the Opportunity.
&lt;/h1&gt;

&lt;p&gt;This is a subtle but important difference.&lt;/p&gt;

&lt;p&gt;If your entire pitch is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I can't pay you, but I'll give you equity."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;you're focusing on what you don't have.&lt;/p&gt;

&lt;p&gt;Instead, explain what the person is joining.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We're solving a problem for small property managers. We've interviewed 30 potential customers, built the first prototype, and have 12 businesses interested in testing it. I'm handling the business, customer development, and sales. I'm looking for a technical partner to own the product engineering side. I can offer a small monthly stipend plus equity."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now the conversation is completely different.&lt;/p&gt;

&lt;p&gt;The person can evaluate:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Founder + Problem + Market + Progress + Role + Compensation + Opportunity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's a real decision.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 8: Show What You Are Bringing to the Table
&lt;/h1&gt;

&lt;p&gt;If you aren't technical, you still have a lot to contribute.&lt;/p&gt;

&lt;p&gt;Don't approach a technical partner as if their job is to build your idea while you simply wait.&lt;/p&gt;

&lt;p&gt;Your contribution might be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer research&lt;/li&gt;
&lt;li&gt;Sales&lt;/li&gt;
&lt;li&gt;Marketing&lt;/li&gt;
&lt;li&gt;Industry knowledge&lt;/li&gt;
&lt;li&gt;Product strategy&lt;/li&gt;
&lt;li&gt;Partnerships&lt;/li&gt;
&lt;li&gt;Fundraising&lt;/li&gt;
&lt;li&gt;Customer support&lt;/li&gt;
&lt;li&gt;Operations&lt;/li&gt;
&lt;li&gt;Business development&lt;/li&gt;
&lt;li&gt;Vision&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A strong technical partner should feel that you're building the company &lt;strong&gt;together&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not that they're being hired to turn your idea into reality while you sit on the sidelines.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 9: Understand the Difference Between an MVP and Production Software
&lt;/h1&gt;

&lt;p&gt;This is where many non-technical founders get confused.&lt;/p&gt;

&lt;p&gt;You may have an AI-generated prototype that looks fantastic.&lt;/p&gt;

&lt;p&gt;That's great.&lt;/p&gt;

&lt;p&gt;But production software has another layer of complexity.&lt;/p&gt;

&lt;p&gt;Someone needs to make sure things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User accounts&lt;/li&gt;
&lt;li&gt;Data&lt;/li&gt;
&lt;li&gt;Payments&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Permissions&lt;/li&gt;
&lt;li&gt;Backups&lt;/li&gt;
&lt;li&gt;Performance&lt;/li&gt;
&lt;li&gt;Reliability&lt;/li&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;are handled properly.&lt;/p&gt;

&lt;p&gt;Your first prototype doesn't need to solve every problem.&lt;/p&gt;

&lt;p&gt;But once real customers depend on your software, the standard changes.&lt;/p&gt;

&lt;p&gt;That's when experienced technical help becomes especially valuable.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 10: Don't Build Everything
&lt;/h1&gt;

&lt;p&gt;Your first SaaS version should be much smaller than your dream product.&lt;/p&gt;

&lt;p&gt;Suppose your long-term vision includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web application&lt;/li&gt;
&lt;li&gt;Mobile apps&lt;/li&gt;
&lt;li&gt;AI assistant&lt;/li&gt;
&lt;li&gt;20 integrations&lt;/li&gt;
&lt;li&gt;Advanced analytics&lt;/li&gt;
&lt;li&gt;Automated workflows&lt;/li&gt;
&lt;li&gt;Team collaboration&lt;/li&gt;
&lt;li&gt;Enterprise permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't build all of that.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is the one workflow that proves this product is useful?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Build that first.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User signs up
      ↓
Creates a project
      ↓
Uses the core feature
      ↓
Gets the promised result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that works, you have something worth improving.&lt;/p&gt;




&lt;h1&gt;
  
  
  Step 11: Use Your Limited Budget Carefully
&lt;/h1&gt;

&lt;p&gt;Having a small budget doesn't mean you should refuse to spend anything.&lt;/p&gt;

&lt;p&gt;It means you need to spend intentionally.&lt;/p&gt;

&lt;p&gt;Your first $100 might be more valuable when spent on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer research&lt;/li&gt;
&lt;li&gt;A domain&lt;/li&gt;
&lt;li&gt;Essential software&lt;/li&gt;
&lt;li&gt;A small AI-tool subscription&lt;/li&gt;
&lt;li&gt;Early marketing experiments&lt;/li&gt;
&lt;li&gt;Professional design help&lt;/li&gt;
&lt;li&gt;Technical review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;rather than building ten features that nobody asked for.&lt;/p&gt;

&lt;p&gt;Ask yourself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is the biggest uncertainty in my startup right now?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then spend money to reduce that uncertainty.&lt;/p&gt;

&lt;p&gt;If nobody wants the product, you need validation.&lt;/p&gt;

&lt;p&gt;If people want it but the prototype doesn't work reliably, you need engineering.&lt;/p&gt;

&lt;p&gt;If the product works but nobody knows about it, you need distribution.&lt;/p&gt;

&lt;p&gt;Your spending priorities should change as you learn.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Simple Path for a Founder With Almost No Budget
&lt;/h1&gt;

&lt;p&gt;If I were starting from zero today, I'd think about it like this:&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1 — Idea
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Budget: $0&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Problem&lt;/li&gt;
&lt;li&gt;Customer&lt;/li&gt;
&lt;li&gt;Solution&lt;/li&gt;
&lt;li&gt;Business model&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stage 2 — Presentation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Budget: $0&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product brief&lt;/li&gt;
&lt;li&gt;Pitch deck&lt;/li&gt;
&lt;li&gt;Screenshots&lt;/li&gt;
&lt;li&gt;Roadmap&lt;/li&gt;
&lt;li&gt;Team requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stage 3 — Landing Page
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Budget: $0–$10+&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a professional website and start collecting interested users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 4 — Prototype
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Budget: $0–$10+&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use AI tools to build the first visual version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 5 — Validation
&lt;/h3&gt;

&lt;p&gt;Talk to potential customers.&lt;/p&gt;

&lt;p&gt;Get feedback.&lt;/p&gt;

&lt;p&gt;Find beta users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 6 — Find the Technical Partner
&lt;/h3&gt;

&lt;p&gt;Present:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The product&lt;/li&gt;
&lt;li&gt;The problem&lt;/li&gt;
&lt;li&gt;Your research&lt;/li&gt;
&lt;li&gt;Your prototype&lt;/li&gt;
&lt;li&gt;Your traction&lt;/li&gt;
&lt;li&gt;Your roadmap&lt;/li&gt;
&lt;li&gt;The role&lt;/li&gt;
&lt;li&gt;The expected commitment&lt;/li&gt;
&lt;li&gt;Equity&lt;/li&gt;
&lt;li&gt;Stipend, if possible&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stage 7 — Build Production Software
&lt;/h3&gt;

&lt;p&gt;Once the idea has enough evidence behind it, bring in experienced engineering to turn the prototype into a reliable SaaS.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Made Me Say Yes to a Zero-Budget Founder
&lt;/h1&gt;

&lt;p&gt;This isn't theoretical for me.&lt;/p&gt;

&lt;p&gt;Recently, a founder approached me about helping scale their SaaS.&lt;/p&gt;

&lt;p&gt;They had essentially no budget.&lt;/p&gt;

&lt;p&gt;They were still building their team and were offering equity across technical, sales, and marketing roles.&lt;/p&gt;

&lt;p&gt;But I didn't say yes because of the equity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I said yes because of the clarity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The founder clearly explained:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What they were building&lt;/li&gt;
&lt;li&gt;What problem they were solving&lt;/li&gt;
&lt;li&gt;What they had already accomplished&lt;/li&gt;
&lt;li&gt;What they needed&lt;/li&gt;
&lt;li&gt;What they were offering&lt;/li&gt;
&lt;li&gt;Where they wanted to go&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I didn't have to guess.&lt;/p&gt;

&lt;p&gt;I didn't have to spend hours figuring out what the opportunity was.&lt;/p&gt;

&lt;p&gt;They had done the work to make the opportunity understandable.&lt;/p&gt;

&lt;p&gt;That matters.&lt;/p&gt;




&lt;h1&gt;
  
  
  Your Lack of Budget Doesn't Have to Be Your Biggest Weakness
&lt;/h1&gt;

&lt;p&gt;You probably can't compete with a funded startup on salary.&lt;/p&gt;

&lt;p&gt;That's okay.&lt;/p&gt;

&lt;p&gt;You can compete on other things.&lt;/p&gt;

&lt;p&gt;You can show:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Preparation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ownership.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clarity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traction.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vision.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commitment.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A developer might reject an opportunity with a large salary if the product, founder, or problem isn't compelling.&lt;/p&gt;

&lt;p&gt;And someone might consider an equity-based opportunity if they genuinely believe:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This is something worth building."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That doesn't mean you should expect people to work indefinitely without compensation.&lt;/p&gt;

&lt;p&gt;It means you should create an opportunity where the risks, responsibilities, and potential rewards are clearly understood by everyone involved.&lt;/p&gt;




&lt;h1&gt;
  
  
  Don't Let "No Budget" Become "No Action"
&lt;/h1&gt;

&lt;p&gt;The worst thing you can do with a small budget is nothing.&lt;/p&gt;

&lt;p&gt;You don't need to have everything figured out.&lt;/p&gt;

&lt;p&gt;Start with what you can control.&lt;/p&gt;

&lt;p&gt;Build the landing page.&lt;/p&gt;

&lt;p&gt;Create the prototype.&lt;/p&gt;

&lt;p&gt;Talk to customers.&lt;/p&gt;

&lt;p&gt;Write the product presentation.&lt;/p&gt;

&lt;p&gt;Find out whether people care.&lt;/p&gt;

&lt;p&gt;Document what you've learned.&lt;/p&gt;

&lt;p&gt;Then approach the technical people you need.&lt;/p&gt;

&lt;p&gt;Don't say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"I have an idea. Can you build it?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Show them:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Here's what I'm building. Here's the problem. Here's what I've already done. Here's what I've learned. Here's what I need from you. Here's what I can offer. And here's where I want to take this."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a completely different conversation.&lt;/p&gt;

&lt;p&gt;When you don't have a big budget, &lt;strong&gt;clarity becomes part of your compensation package.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Budget tells people what you can pay.&lt;/p&gt;

&lt;p&gt;Clarity tells people whether they can trust the opportunity.&lt;/p&gt;

&lt;p&gt;Sometimes the right technical partner isn't looking for the biggest paycheck.&lt;/p&gt;

&lt;p&gt;They're looking for something worth building.&lt;/p&gt;

&lt;p&gt;I'm Haseeb, building SaaS products at Seebify. The full founder-focused version of this topic is here: &lt;a href="https://www.seebify.com/blog/how-to-build-saas-mvp-with-no-budget" rel="noopener noreferrer"&gt;https://www.seebify.com/blog/how-to-build-saas-mvp-with-no-budget&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>sass</category>
      <category>entrepreneurship</category>
      <category>startup</category>
    </item>
    <item>
      <title>Scaling a Node.js + PostgreSQL SaaS Backend: Pagination, Redis Caching, and Background Jobs</title>
      <dc:creator>Haseeb Sheikh</dc:creator>
      <pubDate>Tue, 25 Aug 2026 11:46:07 +0000</pubDate>
      <link>https://dev.to/haseeb_sheikh_0f627e74ba7/scaling-a-nodejs-postgresql-saas-backend-pagination-redis-caching-and-background-jobs-3m8h</link>
      <guid>https://dev.to/haseeb_sheikh_0f627e74ba7/scaling-a-nodejs-postgresql-saas-backend-pagination-redis-caching-and-background-jobs-3m8h</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuh8o58l19jvd726sgk7q.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuh8o58l19jvd726sgk7q.jpg" alt=" " width="799" height="436"&gt;&lt;/a&gt;One backend. One PostgreSQL database. An admin panel, a web app, and two mobile clients all hitting the same API.&lt;/p&gt;

&lt;p&gt;It worked fine — until it didn't. &lt;strong&gt;p99 latency crept past 4 seconds, the job queue started backing up, and the instinct was to add more servers.&lt;/strong&gt; That instinct is almost always wrong. Here's the actual sequence that fixed it, with the code — not just the theory.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Measure before you touch anything&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Don't scale based on a feeling. Track these five things before changing infrastructure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CPU/RAM&lt;/strong&gt; on your app servers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API latency&lt;/strong&gt; — p95 and p99, not the average (a 200ms average can hide a p99 of 4s)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query latency, active connections, lock contention&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error rate per endpoint&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Queue depth and job processing time&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A basic dashboard covering these tells you whether the problem is application, database, or queue — before you spend money on the wrong layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Make the application do less work first&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is the cheapest scaling win, and the one most often skipped.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Cursor-based pagination instead of large offsets&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM orders WHERE id &amp;gt; $1 ORDER BY id LIMIT $2`&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`SELECT * FROM orders ORDER BY id LIMIT $1`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cursor&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;nextCursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&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;An indexed &lt;code&gt;WHERE id &amp;gt; cursor&lt;/code&gt; avoids the cost of skipping hundreds of thousands of rows just to discard them — unlike &lt;code&gt;OFFSET 50000&lt;/code&gt;, which still scans everything before it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Kill N+1 queries&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Fetching 100 orders, then querying the customer for each one separately, turns &lt;strong&gt;1 query into 101&lt;/strong&gt;:&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="c1"&gt;-- Before: 1 query + 100 more in a loop&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- After: 1 query&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dangerous part: &lt;strong&gt;N+1 is invisible with 10 records and painful with 10,000.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;PostgreSQL: indexes, EXPLAIN ANALYZE, connection pooling&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_orders_user_id&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYZE&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&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;EXPLAIN ANALYZE&lt;/code&gt; shows you sequential scans, bad row estimates, and unnecessary sorts — &lt;strong&gt;don't guess why a query is slow, look.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Five app servers each opening 20 raw connections is &lt;strong&gt;100 connections fast&lt;/strong&gt;, and Postgres has a hard limit. Pool them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Pool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                     &lt;span class="c1"&gt;// per instance, not global&lt;/span&gt;
  &lt;span class="na"&gt;idleTimeoutMillis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;connectionTimeoutMillis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2000&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;For anything beyond a handful of app instances, put &lt;strong&gt;PgBouncer&lt;/strong&gt; in front of Postgres so pooling is handled centrally instead of per-process.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Redis: cache-aside, not a database&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Cache-aside is the default pattern: &lt;strong&gt;check Redis → fall back to Postgres on a miss → write through → return.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ioredis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)();&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getDashboardSummary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`dashboard:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;computeDashboardSummary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// hits PostgreSQL&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 60s TTL&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;summary&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;The hard part isn't reading from cache, it's &lt;strong&gt;invalidation&lt;/strong&gt; — when the underlying data changes, update or evict the key, or you'll serve a stale permission set long after it changed.&lt;/p&gt;

&lt;p&gt;Redis also isn't your source of truth; use it for &lt;strong&gt;caching, rate limiting, and sessions&lt;/strong&gt;, not durable business data.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Background jobs, with real retry logic&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Your API shouldn't block on report generation, PDF exports, or bulk email. Using &lt;strong&gt;BullMQ&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Worker&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bullmq&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Producer — API route&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reportQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reports&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;redisConnection&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/reports&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reportQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;generate-report&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Worker — separate process&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reports&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateAndStoreReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;redisConnection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;exponential&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&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;strong&gt;Retries alone aren't enough&lt;/strong&gt; — a worker can crash &lt;em&gt;after&lt;/em&gt; sending an email but &lt;em&gt;before&lt;/em&gt; marking the job done, and the retry will fire again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invoices&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;invoiceId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alreadySent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`invoice-sent:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;alreadySent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// retried, but the email already went out&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendInvoiceEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`invoice-sent:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;invoiceId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;86400&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;Without that idempotency check, a retried job means &lt;strong&gt;a duplicate invoice email in someone's inbox.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Rate limiting, tiered by plan&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;An endpoint that normally sees 100 req/min can suddenly see 20,000 — spike, bad integration, or abuse. Enforce limits consistently across instances via Redis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rateLimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express-rate-limit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RedisStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rate-limit-redis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;limiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rateLimit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;store&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RedisStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;sendCommand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;windowMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;enterprise&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limiter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Gotchas that don't show up until production&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;N+1 queries are invisible at small scale.&lt;/strong&gt; They only hurt once a customer has thousands of rows, by which point they're already in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgotten cache invalidation&lt;/strong&gt; serves stale data silently — a user balance or permission set that's wrong for minutes or hours with no error thrown anywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-memory session state breaks the moment you add a second node.&lt;/strong&gt; &lt;code&gt;const sessions = {}&lt;/code&gt; living in process memory means a request routed to a different instance won't see it. Sessions, cache, and uploaded files need to live in Postgres, Redis, or object storage — not app memory — before you run more than one instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jobs without idempotency duplicate side effects on retry&lt;/strong&gt;, not just waste compute — a resent email or a double-charged webhook is a worse bug than a slow query.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Scale infrastructure last, not first&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once the application and database are genuinely optimized: &lt;strong&gt;vertical scaling&lt;/strong&gt; (bigger machine) is simple but has a ceiling. &lt;strong&gt;Horizontal scaling&lt;/strong&gt; (more machines behind a load balancer) needs a stateless app first — shared sessions, shared cache, shared storage — or you'll get inconsistent behavior depending on which node a request hits.&lt;/p&gt;

&lt;p&gt;Skip &lt;strong&gt;Kubernetes and microservices&lt;/strong&gt; until there's a concrete reason — a workload that needs to scale independently, or a team boundary large enough to justify separate deploy lifecycles.&lt;/p&gt;

&lt;p&gt;Neither one makes a slow query fast or an unindexed table efficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Takeaway&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Measure → Optimize → Test → Scale&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Most of the wins above cost nothing but engineering time — &lt;strong&gt;pagination, an index, a cache with a sane TTL, moving slow work off the request path.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Infrastructure is the &lt;strong&gt;last lever to pull, not the first.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Haseeb — I build production SaaS backends (Node.js, PostgreSQL, Redis, Stripe) at &lt;a href="https://www.seebify.com" rel="noopener noreferrer"&gt;Seebify&lt;/a&gt;. Full write-up with the business-side reasoning and a complete stage-by-stage scaling roadmap &lt;a href="https://www.seebify.com/blog/how-to-scale-saas-practical-guide" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>postgressql</category>
      <category>backend</category>
      <category>redis</category>
    </item>
    <item>
      <title>How I Built a $0/Month Ecommerce MVP Without Shopify</title>
      <dc:creator>Haseeb Sheikh</dc:creator>
      <pubDate>Sun, 09 Aug 2026 11:15:10 +0000</pubDate>
      <link>https://dev.to/haseeb_sheikh_0f627e74ba7/how-i-built-a-0month-ecommerce-mvp-without-shopify-7hh</link>
      <guid>https://dev.to/haseeb_sheikh_0f627e74ba7/how-i-built-a-0month-ecommerce-mvp-without-shopify-7hh</guid>
      <description>&lt;p&gt;A founder came to me with a simple requirement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100+ products&lt;/li&gt;
&lt;li&gt;Customers need to browse products&lt;/li&gt;
&lt;li&gt;Customers need to place orders through WhatsApp&lt;/li&gt;
&lt;li&gt;No online payments&lt;/li&gt;
&lt;li&gt;No monthly Shopify subscription&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interesting part?&lt;/p&gt;

&lt;p&gt;He didn't need a traditional ecommerce platform.&lt;/p&gt;

&lt;p&gt;He needed a simple way to display products and receive orders.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;The original idea could easily turn into a large ecommerce application:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User accounts&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Shopping cart&lt;/li&gt;
&lt;li&gt;Checkout&lt;/li&gt;
&lt;li&gt;Payment gateway&lt;/li&gt;
&lt;li&gt;Order management&lt;/li&gt;
&lt;li&gt;Email notifications&lt;/li&gt;
&lt;li&gt;Product management&lt;/li&gt;
&lt;li&gt;Inventory&lt;/li&gt;
&lt;li&gt;Hosting infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But most of these weren't actually required.&lt;/p&gt;

&lt;p&gt;The business only needed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Products → Cart → WhatsApp order&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So we started there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Built
&lt;/h2&gt;

&lt;p&gt;The customer-facing side has three main steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Browse products&lt;/li&gt;
&lt;li&gt;Add products to the cart&lt;/li&gt;
&lt;li&gt;Send the order directly to WhatsApp&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The admin panel handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding products&lt;/li&gt;
&lt;li&gt;Editing products&lt;/li&gt;
&lt;li&gt;Uploading product images&lt;/li&gt;
&lt;li&gt;Setting prices&lt;/li&gt;
&lt;li&gt;Managing inventory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no unnecessary checkout flow or payment system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;For this MVP, we intentionally kept the architecture simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Next.js&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The customer website and admin interface are built with Next.js.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hosting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vercel&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The application can be deployed on Vercel's free tier, which removes the need for a traditional server for this MVP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Supabase&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The product data is stored in Supabase using its free tier.&lt;/p&gt;

&lt;p&gt;The goal was to avoid paying for infrastructure before the business had validated the idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Didn't Build Payments
&lt;/h2&gt;

&lt;p&gt;The founder didn't need online payments.&lt;/p&gt;

&lt;p&gt;Customers were going to place their orders through WhatsApp.&lt;/p&gt;

&lt;p&gt;Adding Stripe or another payment provider would have introduced additional complexity without solving an actual business problem.&lt;/p&gt;

&lt;p&gt;So we didn't build it.&lt;/p&gt;

&lt;p&gt;The same principle applied to authentication and email notifications.&lt;/p&gt;

&lt;p&gt;If a feature isn't needed to validate the business, it can wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  The $0/Month MVP
&lt;/h2&gt;

&lt;p&gt;With this architecture, the initial infrastructure cost can be &lt;strong&gt;$0/month&lt;/strong&gt; using the free tiers of Vercel and Supabase.&lt;/p&gt;

&lt;p&gt;That doesn't mean every ecommerce business can run indefinitely for free.&lt;/p&gt;

&lt;p&gt;As traffic, storage, database usage, or business requirements grow, paid infrastructure may become necessary.&lt;/p&gt;

&lt;p&gt;The goal here was different:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validate the business before paying for infrastructure.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Add Later
&lt;/h2&gt;

&lt;p&gt;If the business starts getting real traction, we can introduce features based on actual needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Online payments&lt;/li&gt;
&lt;li&gt;Customer accounts&lt;/li&gt;
&lt;li&gt;Order tracking&lt;/li&gt;
&lt;li&gt;Automated notifications&lt;/li&gt;
&lt;li&gt;Advanced inventory management&lt;/li&gt;
&lt;li&gt;Analytics&lt;/li&gt;
&lt;li&gt;More powerful admin features&lt;/li&gt;
&lt;li&gt;Better order management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part is that these features can be added later.&lt;/p&gt;

&lt;p&gt;We don't need to build everything on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bigger Lesson
&lt;/h2&gt;

&lt;p&gt;Good engineering isn't about building the biggest system possible.&lt;/p&gt;

&lt;p&gt;It's about understanding the actual problem and choosing the simplest architecture that solves it.&lt;/p&gt;

&lt;p&gt;In this case, the business didn't need Shopify.&lt;/p&gt;

&lt;p&gt;It needed a product catalog and a way to turn a customer's cart into a WhatsApp order.&lt;/p&gt;

&lt;p&gt;So that's what we built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start with what the business actually needs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then add complexity when the business earns the right to need it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>database</category>
      <category>sass</category>
    </item>
  </channel>
</rss>
