Skip to content
Back to Blogs
System DesignSaaSScalabilityNginxBackend

Building and Scaling a Manufacturing SaaS From the First Proposal

Lessons from evolving a manufacturing SaaS product from a single server into a scalable application, covering performance, shared data, load balancing, and background processing.

D
Dheeraj Jha
October 22, 2026
7 min read

Updated October 22, 2026

Building and Scaling a Manufacturing SaaS From the First Proposal

For more than a year, I have been contributing to a SaaS application for the manufacturing industry. I joined at the initial proposal stage and have seen it grow into a product used by multiple B2B businesses.

The experience changed how I think about system design. Each new requirement exposed a different constraint: repeated database reads, limited server capacity, shared data, or work that took too long to finish inside a request. Here is how those decisions developed.

How everything started

Manufacturing businesses were sharing product configurations with customers through PDFs. Those documents contained specifications and configuration details, but the client wanted an interactive web application where users could enter the same information and visualise parts of the product in 3D.

I started by understanding the requirements and preparing the proposal and architecture overview. Our first goal was a working product. As the application grew, we added:

  • Product configuration and 3D visualisation.
  • Order and configuration management.
  • Automatic PDF generation.
  • Delivery of documents to customers.

As more businesses began using these features, performance and infrastructure needed more attention.

Issue 1: Why was the application slow?

We were repeatedly retrieving sessions, roles, and other information from the database. Those unnecessary reads added work to the backend and increased response times. Before changing the server, we needed to understand which requests were slow and where they spent their time.

We improved repeated database access and how the client managed state. Browser storage helped with some non sensitive information, but authentication and permission checks remained on the server.

The distinction matters: a user can change browser storage. It can hold a display preference or cached UI data, but it cannot decide whether someone is allowed to perform an operation. The backend must independently validate that permission.

My first lesson was to measure the slow path before scaling the infrastructure. More hardware can hide unnecessary work for a while; removing that work can benefit every request.

Issue 2: What happens when users and orders increase?

More customers meant more orders being created and updated. Configurations became more detailed, and 3D assets and generated PDFs added storage and processing demands. At that stage, we increased the resources available to the existing server.

This is vertical scaling: increasing CPU, RAM, or other capacity on one machine. It was a practical choice because it addressed the immediate limit without adding multiple application instances to operate.

A larger machine still has limits. It also leaves the application dependent on that machine. Once one instance becomes the constraint, increasing its resources may no longer be enough.

Issue 3: How do we handle more concurrent requests?

Horizontal scaling means running multiple application instances so that requests can be handled by different servers. It can increase capacity when requests can run independently and the database and other dependencies can support the additional traffic.

Introducing more instances also means answering three questions:

  1. Routing: how will incoming requests reach an available server?
  2. Sessions: can another instance recognise the user and validate the request?
  3. Business data: will every instance read the same orders and configurations?

These are separate design decisions. Adding a second process does not resolve them automatically.

Issue 4: How do we maintain consistent data?

Suppose a user creates an order through application server one. Their next request reaches application server two. They should still see the same order and configuration details, regardless of which instance handled the first request.

A shared relational database can support this arrangement. Each application instance connects to the same database, which remains the source of truth for business records.

Two application instances, one source of truth
User requestsRequests may reach different instances.
Load balancerForwards each request to an available application instance.
Application server 1 or server 2Both validate permissions and use the same business records.
Shared PostgreSQL databaseStores orders and configurations independently of the application process.

The rules I keep in mind are:

  • Keep business records in shared storage. An order should not exist only in one process’s memory or local files.
  • Protect writes with database rules. Transactions and constraints help preserve valid state when requests arrive concurrently.
  • Handle sessions deliberately. Use an appropriate shared session store or stateless authentication strategy.
  • Scale the database according to its workload. Multiple application instances do not automatically require a distributed database.

A shared database gives every instance access to the same records. Correctness still depends on the transaction boundaries, isolation, and update logic. For example, two requests changing the same order may need a version check or locking strategy.

If the database becomes slow, investigate its queries, indexes, connection limits, and contention. Caching or read replicas may help particular workloads, but replica lag and cache freshness also become part of the design. Application scaling and database scaling solve different constraints.

Issue 5: How does Nginx distribute requests?

Nginx can act as a reverse proxy and load balancer. It receives incoming traffic and forwards requests to application instances according to the configured balancing strategy.

That distributes application work, but it does not remove every bottleneck. If all instances wait on the same slow database query, adding instances may simply create more waiting requests. Likewise, sessions stored only in one process can cause inconsistent login behaviour when a request reaches another instance.

The load balancer, session strategy, and database capacity therefore need to be considered together. I found it more useful to follow the complete request path than to judge each component in isolation.

Issue 6: Handling images, PDFs, and 3D assets

Our application deals with files as well as database records. Product images, 3D assets, and generated PDFs consume storage and bandwidth; document generation can also take significant processing time.

Two improvements are worth evaluating as those demands grow:

  • Object storage for uploaded assets and generated documents, with ownership and metadata kept in the database.
  • A durable queue and background workers for operations such as PDF generation and document delivery.

For example, the application can save an order, record a PDF generation job, and return the order’s status. A worker then generates the document and makes it available to the customer. The UI can show that the document is still being prepared.

This moves processing out of the request, but the job still needs retries, failure handling, and status tracking. These are patterns to evaluate against the workload, rather than claims that every part of this application already uses them.

What I learned from this journey

System design continued throughout the project. Early on, I focused on making the product work. Later, the questions became more specific: which database reads can we remove, which resource is saturated, and which operation should move out of the user’s request?

The most useful decisions followed those questions. Sometimes an efficient query was enough. Sometimes the server needed more resources. Sometimes the application needed a shared state model or a separate worker.

Working from the initial proposal through production helped me see architecture as an ongoing engineering responsibility. The aim is to keep the software understandable and dependable as the business grows.