DEV Community

Cover image for How to Evaluate a SaaS Development Company: Architecture, Code, and Production Readiness
Haseeb Sheikh
Haseeb Sheikh

Posted on

How to Evaluate a SaaS Development Company: Architecture, Code, and Production Readiness

Hiring a SaaS development company is often treated as a pricing decision:

Company A costs $10k, Company B costs $20k, Company C costs $30k.

That comparison tells you very little.

For a SaaS product, the expensive problems usually appear after the first release: slow queries, inconsistent business logic, broken permissions, unreliable webhooks, difficult deployments, and an architecture that becomes painful to change.

A better approach is to evaluate the engineering system before you evaluate the sales pitch.

Here are the technical areas I would inspect.

1. Start With the Architecture Diagram

Before looking at individual technologies, ask the development team to draw the system.

For a typical SaaS application, you might see something like:

                    ┌─────────────────┐
                    │   Next.js App   │
                    └────────┬────────┘
                             │
                       HTTPS / API
                             │
                    ┌────────▼────────┐
                    │   Node.js API   │
                    └──────┬───┬──────┘
                           │   │
                 ┌─────────┘   └──────────┐
                 │                        │
          ┌──────▼──────┐          ┌──────▼──────┐
          │ PostgreSQL  │          │    Redis    │
          └─────────────┘          └─────────────┘
                 │
          ┌──────▼──────┐
          │   Workers   │
          └─────────────┘

        External services:
        Stripe / Email / Storage / OAuth
Enter fullscreen mode Exit fullscreen mode

The diagram itself isn't the important part.

The important part is whether the team can explain why each component exists.

For example:

  • PostgreSQL stores transactional business data.
  • Redis can handle caching, rate limits, or short-lived state.
  • Workers handle jobs that shouldn't block an API request.
  • Stripe handles payment processing.
  • Object storage handles files instead of putting large files directly into the database.

If a team cannot explain these boundaries, I would keep asking questions before signing the project.

2. Look at the Database Before the UI

A SaaS product can have a beautiful frontend and still have a poor architecture underneath it.

Consider a simple multi-tenant SaaS.

A starting PostgreSQL model might look like:

CREATE TABLE organizations (
  id UUID PRIMARY KEY,
  name TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE users (
  id UUID PRIMARY KEY,
  email TEXT NOT NULL UNIQUE,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE organization_members (
  organization_id UUID NOT NULL REFERENCES organizations(id),
  user_id UUID NOT NULL REFERENCES users(id),
  role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'member')),
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),

  PRIMARY KEY (organization_id, user_id)
);
Enter fullscreen mode Exit fullscreen mode

Now imagine an projects table:

CREATE TABLE projects (
  id UUID PRIMARY KEY,
  organization_id UUID NOT NULL REFERENCES organizations(id),
  name TEXT NOT NULL,
  status TEXT NOT NULL DEFAULT 'active',
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_projects_organization_id
  ON projects(organization_id);
Enter fullscreen mode Exit fullscreen mode

That organization_id is important.

It creates an explicit relationship between the record and the tenant that owns it.

The API should then enforce that relationship.

For example:

const { rows } = await db.query(
  `
  SELECT *
  FROM projects
  WHERE id = $1
    AND organization_id = $2
  `,
  [projectId, organizationId]
);
Enter fullscreen mode Exit fullscreen mode

Instead of:

const { rows } = await db.query(
  `SELECT * FROM projects WHERE id = $1`,
  [projectId]
);
Enter fullscreen mode Exit fullscreen mode

The second query may look harmless, but if tenant isolation is handled incorrectly elsewhere, it can become a serious security problem.

This is one reason I prefer evaluating the data model and authorization rules, not just the screens.

3. Ask How Authentication and Authorization Are Separated

Authentication answers:

"Who are you?"

Authorization answers:

"What are you allowed to do?"

They are not the same thing.

A basic authorization middleware might look like:

function requireRole(...allowedRoles) {
  return (req, res, next) => {
    const role = req.user?.role;

    if (!role || !allowedRoles.includes(role)) {
      return res.status(403).json({
        message: 'Forbidden'
      });
    }

    next();
  };
}
Enter fullscreen mode Exit fullscreen mode

Then:

app.delete(
  '/api/projects/:id',
  authenticate,
  requireRole('owner', 'admin'),
  deleteProject
);
Enter fullscreen mode Exit fullscreen mode

The important question when evaluating a development team isn't whether they can write this middleware.

It's whether they have a consistent authorization model across the application.

Ask:

  • Where is the authenticated user stored?
  • How are roles represented?
  • Can permissions differ by organization?
  • Can a user belong to multiple organizations?
  • Are authorization checks performed server-side?
  • What prevents users from accessing another tenant's records?

A frontend check such as:

if (user.role === 'admin') {
  showDeleteButton();
}
Enter fullscreen mode Exit fullscreen mode

is useful for the interface.

It is not security.

The server still needs to enforce the permission.

4. Examine How They Handle Payments

Stripe integration is another area where a demo can look correct while the production implementation is fragile.

A common mistake is treating the browser response as the final source of truth.

For example:

User clicks Pay
       ↓
Stripe payment
       ↓
Frontend receives success
       ↓
Frontend marks subscription as active
Enter fullscreen mode Exit fullscreen mode

A more reliable architecture is:

User starts payment
       ↓
Stripe processes payment
       ↓
Stripe sends webhook
       ↓
Backend verifies webhook
       ↓
Database updates subscription
       ↓
Application reads subscription state
Enter fullscreen mode Exit fullscreen mode

A simplified webhook handler in Node.js might look like:

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.post(
  '/api/stripe/webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['stripe-signature'];

    let event;

    try {
      event = stripe.webhooks.constructEvent(
        req.body,
        signature,
        process.env.STRIPE_WEBHOOK_SECRET
      );
    } catch (error) {
      return res.status(400).send(`Webhook Error: ${error.message}`);
    }

    switch (event.type) {
      case 'checkout.session.completed':
        // Update order/subscription state in PostgreSQL
        break;

      case 'customer.subscription.updated':
        // Synchronize subscription status
        break;

      case 'customer.subscription.deleted':
        // Mark subscription as canceled
        break;
    }

    return res.json({ received: true });
  }
);
Enter fullscreen mode Exit fullscreen mode

There are several details worth asking about:

Is the webhook signature verified?

Are events handled idempotently?

What happens if Stripe retries the same event?

What happens if the webhook arrives before another database transaction finishes?

These questions reveal much more engineering maturity than asking whether someone has "worked with Stripe."

5. Ask How They Handle Background Work

Not every operation belongs inside the HTTP request.

Imagine a user uploads a large file and your application needs to:

  1. Store the file
  2. Extract its contents
  3. Generate a thumbnail
  4. Send an email
  5. Update analytics

Doing everything inside one request can create slow responses and unreliable failures.

A better pattern is:

HTTP request
     ↓
Create database record
     ↓
Add job to queue
     ↓
Return response
     ↓
       Worker
         ↓
Process job
         ↓
Update database
Enter fullscreen mode Exit fullscreen mode

The exact queue technology can vary.

The engineering principle is what matters.

Ask your potential development partner:

"Which operations will happen synchronously, and which ones should be asynchronous?"

A good answer should be based on the product's workload rather than a preference for a particular library.

6. Look for Production Engineering, Not Just Development

A project isn't production-ready because the page works locally.

Ask how the team handles:

Development
    ↓
Pull Request
    ↓
Code Review
    ↓
Automated Tests
    ↓
Staging
    ↓
Production
Enter fullscreen mode Exit fullscreen mode

Then ask what happens when production breaks.

You want answers around:

  • Error monitoring
  • Structured logging
  • Database backups
  • Deployment rollback
  • Environment management
  • Health checks
  • Database migrations
  • Rate limiting
  • Monitoring
  • Incident recovery

One question I like asking is:

"What happens if the production deployment fails halfway through?"

There isn't one universal answer, but the team should have thought about it.

7. Review the Code Ownership Model

This part is less exciting than architecture diagrams, but it matters.

Make sure the development relationship doesn't leave your product dependent on one company forever.

Clarify ownership of:

Source code
Database
Cloud infrastructure
Domain
Design files
Deployment accounts
Third-party services
Documentation
CI/CD configuration
Enter fullscreen mode Exit fullscreen mode

The most important test is simple:

"Could another competent engineering team take over this project six months from now?"

A healthy codebase should make that possible.

Good documentation, environment configuration, database migrations, deployment instructions, and clear repository structure all contribute to that.

8. Don't Judge a Team by the Number of Developers

A 20-person team doesn't automatically produce a better SaaS architecture than a 3-person team.

For an early-stage product, you may care more about:

  • Who makes architecture decisions?
  • Who writes the backend?
  • Who reviews the code?
  • Who owns deployment?
  • Who understands the business rules?
  • Who talks directly with the founder?
  • Who will still be around after launch?

You want to know who is accountable for the technical system.

Ask to speak with that person before the project starts.

9. Evaluate a Small Technical Example

One useful way to evaluate a development partner is to ask them to explain a small real problem from your product.

For example:

"A user can belong to multiple organizations. Each organization has its own projects. How would you prevent users from reading another organization's projects?"

You can learn a lot from the answer.

A strong response might discuss:

User
 ↓
Organization membership
 ↓
organization_id
 ↓
Project ownership
 ↓
Server-side authorization
Enter fullscreen mode Exit fullscreen mode

A weak response may immediately jump into frontend conditions without discussing the database or API layer.

You don't need to give candidates a huge unpaid test.

A small architecture discussion can reveal a lot.

10. The Biggest Red Flag: Everything Is a "Feature"

A SaaS project isn't just a collection of screens.

Consider:

Dashboard
Users
Billing
Settings
Reports
Notifications
Enter fullscreen mode Exit fullscreen mode

Those labels are almost meaningless without understanding the underlying workflows.

For example, "Billing" could mean:

  • One-time payments
  • Subscriptions
  • Usage-based billing
  • Trials
  • Coupons
  • Failed payments
  • Refunds
  • Invoices
  • Tax
  • Subscription upgrades
  • Downgrades
  • Cancellation

The engineering complexity comes from the business rules, not the number of pages.

That's why a development team should spend time understanding workflows before estimating the project.

A Practical Technical Checklist

Before hiring a SaaS development company, I would want clear answers to these questions:

Architecture

  • What is the proposed system architecture?
  • Why were these technologies selected?
  • How will the system change when usage grows?
  • What is intentionally deferred until later?

Database

  • How is tenant data isolated?
  • What indexes are required?
  • How are migrations managed?
  • Which records are transactional?

Security

  • How are authentication and authorization separated?
  • How are permissions enforced server-side?
  • How are secrets stored?
  • How are sensitive endpoints protected?

Payments

  • Are payment events confirmed through webhooks?
  • Are webhook handlers idempotent?
  • Where is subscription state stored?

Infrastructure

  • How are staging and production separated?
  • How are deployments handled?
  • What monitoring exists?
  • What is the rollback strategy?
  • How are backups tested?

Ownership

  • Who owns the repository?
  • Who controls production infrastructure?
  • Will another developer be able to take over?
  • Is the system documented?

Team

  • Who is the lead engineer?
  • Who reviews code?
  • Who makes architecture decisions?
  • Who will maintain the product after launch?

Final Thought

When evaluating a SaaS development company, don't stop at:

"Can you build this?"

Ask:

"Can you explain how this will work in production?"

That's the difference between evaluating a feature factory and evaluating an engineering partner.

Look at the database.

Look at authorization.

Look at payment flows.

Look at background jobs.

Look at deployments.

Look at code ownership.

And most importantly, ask the engineers why the system is designed the way it is.

I'm Haseeb, founder of Seebify, where I build and scale SaaS products with a focus on production-ready engineering, clean architecture, and long-term maintainability.

Read the full guide on Seebify

Top comments (0)