Why Tech Stack Decisions Matter More Than You Think
When you are building a SaaS product, your tech stack is not just a list of logos for your landing page. It is the foundation that determines how fast you can ship features, how reliably your system runs under load, how secure your users' data is, and how easy it is to onboard new engineers. Get it wrong, and you spend the next three years fighting your own infrastructure instead of building product.
At TulsiX, we spent serious time evaluating our options before writing the first line of CAPilot code. This article is a transparent walkthrough of what we chose, why we chose it, and how it all fits together. If you are a technical founder, an engineering lead, or just someone curious about how modern SaaS products are built in India, this is for you.
The Frontend: React
For the CAPilot frontend, we chose React. Not because it is the most popular framework — though it is — but because it solves the specific problems we face as a practice management platform.
Component Architecture for Complex UIs
CAPilot is not a simple CRUD application. A single screen might show a client's profile, their compliance status across six different filing types, a task list with assignments, recent invoices, and uploaded documents. React's component model lets us build each of these as an isolated, reusable piece and compose them into complex views without the code becoming unmanageable.
We use a clear component hierarchy: page-level components handle routing and data fetching, section components manage layout, and atomic components (buttons, inputs, status badges, date pickers) ensure consistency across the entire application. This means when we update the design of a status badge, it changes everywhere — the client dashboard, the task list, the compliance calendar — without touching each page individually.
Ecosystem and Hiring
The React ecosystem is mature. Libraries like React Router for navigation, React Query (TanStack Query) for server state management, and Zustand for client-side state give us battle-tested solutions for common problems. We are not reinventing the wheel; we are building product features.
Equally important: React is the most widely known frontend framework among Indian engineers. When we hire, we are not asking candidates to learn a niche technology. They can be productive on day one.
TypeScript Everywhere
We use TypeScript across the entire frontend codebase. This is non-negotiable for us. When your application has hundreds of components passing data between them, type safety catches entire categories of bugs before they ever reach a user. The autocomplete and refactoring support in modern editors (we use VS Code) makes development faster, not slower.
TypeScript is not about writing more code. It is about writing correct code — and catching mistakes at compile time instead of in production at 2 AM.
The Backend: .NET 8
For the API layer and business logic, we chose .NET 8 (C#). This surprises some people who expect an Indian startup to be running Node.js or Python. Here is why .NET was the right call for us.
Performance That Scales
.NET 8 is one of the fastest web frameworks available, consistently ranking near the top of the TechEmpower benchmarks. For a SaaS product that needs to handle hundreds of concurrent users querying client records, generating invoices, and running compliance checks, raw performance matters. We do not want to throw hardware at a slow runtime when a faster one is available at the same cost.
The performance advantage becomes especially important during peak periods — ITR filing season, GST return deadlines — when every CA firm on the platform is active simultaneously. .NET handles these spikes without breaking a sweat.
Type Safety and Enterprise Readiness
C# is a strongly typed language with excellent support for patterns that matter in business software: dependency injection, middleware pipelines, data validation attributes, and structured error handling. When you are building financial software — invoicing, billing, compliance tracking — you need guardrails that prevent subtle bugs from corrupting data.
We use Entity Framework Core as our ORM, which gives us type-safe database queries, automatic migration management, and a clean abstraction over SQL Server. Combined with C#'s nullable reference types, we catch null reference errors at compile time rather than discovering them when a user tries to generate an invoice.
The .NET Ecosystem
The .NET ecosystem provides built-in solutions for authentication (ASP.NET Identity), authorization (policy-based), API documentation (Swagger/OpenAPI), background job processing (hosted services), and email sending. We also use libraries like FluentValidation for request validation, AutoMapper for object mapping, and Serilog for structured logging. These are not experimental libraries — they are mature, well-maintained, and used in production by thousands of companies.
The Cloud: Microsoft Azure
Our entire infrastructure runs on Microsoft Azure. This was a deliberate choice driven by several factors.
PaaS-First Architecture
We are a small team. We do not have — and do not want — a dedicated infrastructure team managing Kubernetes clusters, patching Linux servers, or debugging networking issues. Azure's Platform-as-a-Service offerings let us focus on building product while Microsoft handles the infrastructure.
Our architecture uses:
- Azure Static Web Apps for the React frontend — global CDN distribution, automatic HTTPS, custom domains, and zero server management. Deployments happen automatically on every push to the main branch.
- Azure App Service for the .NET API — managed hosting with auto-scaling, health monitoring, deployment slots for zero-downtime releases, and built-in diagnostics.
- Azure SQL Database for our relational data — managed SQL Server with automatic backups, point-in-time restore, geo-replication options, and built-in threat detection.
- Azure Blob Storage for document storage — encrypted at rest, role-based access, and virtually unlimited capacity for client documents.
- Azure Key Vault for secrets management — connection strings, API keys, and JWT signing keys are never stored in code or configuration files.
Why Not AWS or GCP?
Honest answer: all three major cloud providers are excellent, and you can build great products on any of them. We chose Azure because of its tight integration with .NET (unsurprising, given Microsoft builds both), the quality of its PaaS offerings for our specific architecture, and the fact that Azure has data centres in India (Central India and South India regions), which means lower latency for our users.
Azure's pricing for our workload profile — a managed database, a couple of app services, blob storage, and a static web app — is also very competitive. For a bootstrapped product company, cost predictability matters.
The Database: SQL Server
We use Azure SQL Database, which is managed SQL Server in the cloud. For a practice management platform dealing with financial data, a relational database was the obvious choice.
CA firm data is inherently relational. A client has multiple engagements. Each engagement has multiple tasks. Each task has assignments, deadlines, and status updates. Invoices link to clients, services, and tax calculations. This is a domain where foreign keys, transactions, and referential integrity are not optional luxuries — they are requirements.
We considered PostgreSQL (and it would have worked fine), but SQL Server's integration with Entity Framework Core is the most mature, Azure SQL's managed offering is excellent, and our team has deep SQL Server expertise. Sometimes the best technology choice is the one your team knows cold.
We do not choose technologies to be interesting. We choose technologies to be reliable. Our users are running their businesses on our platform — they do not care what database we use, they care that their data is always there when they need it.
Authentication and Authorization
Security is not a feature we add later — it is baked into every layer of the system.
JWT-Based Authentication
CAPilot uses JSON Web Tokens (JWT) for authentication. When a user logs in with their email and password, the server validates credentials, generates a signed JWT containing the user's identity and roles, and returns it to the client. Every subsequent API request includes this token, and the server validates it without needing a database lookup for each request.
We use short-lived access tokens (15 minutes) combined with longer-lived refresh tokens stored in HttpOnly cookies. This means that even if an access token is somehow intercepted, the window of exposure is small. And because refresh tokens are HttpOnly, they are not accessible to JavaScript — mitigating XSS-based token theft.
Role-Based Access Control
CA firms have clear hierarchies — partners, managers, senior associates, article clerks. Not everyone should have access to everything. CAPilot implements role-based access control (RBAC) at both the API level and the UI level:
- API level: Every endpoint is decorated with authorization policies that check the user's role before processing the request. An article clerk cannot access firm-wide billing data, period.
- UI level: Components conditionally render based on the user's permissions. An article clerk does not even see the billing tab — not because we hide it after loading, but because the navigation is built dynamically based on their role.
CI/CD: GitHub Actions to Azure
Our deployment pipeline is fully automated using GitHub Actions. Here is how a code change goes from a developer's machine to production:
- Developer pushes to a feature branch — automated checks run: TypeScript compilation, ESLint, unit tests, .NET build, and API tests.
- Pull request is created — the PR triggers a preview deployment. Reviewers can test the actual running application, not just read code diffs.
- Code review and approval — at least one team member reviews the PR. We use GitHub's CODEOWNERS to ensure changes to critical paths (authentication, billing, data models) require senior review.
- Merge to main — triggers the production deployment pipeline. The frontend is built and deployed to Azure Static Web Apps. The backend is built, tested, and deployed to Azure App Service using deployment slots.
- Health checks — after deployment, automated health checks verify the application is responding correctly. If health checks fail, the deployment is automatically rolled back.
This entire pipeline runs in under 8 minutes. A developer can merge a PR and see their change live in production before their coffee gets cold.
Security-First Approach
When you are handling financial data for CA firms, security is not something you bolt on — it has to be part of the architecture from day one.
Transport Security
Every connection to CAPilot is HTTPS. There is no HTTP endpoint. We use HSTS headers to ensure browsers never downgrade to unencrypted connections. Azure Static Web Apps and App Service both enforce TLS 1.2 minimum.
Content Security Policy
Our frontend sends strict Content Security Policy (CSP) headers that prevent inline scripts, restrict which domains can serve resources, and block common XSS attack vectors. We also set X-Frame-Options: DENY to prevent clickjacking and X-Content-Type-Options: nosniff to prevent MIME-type sniffing attacks.
Input Validation
Every piece of data that enters our system through the API is validated using FluentValidation rules. We validate not just types and lengths, but business rules — a GST number must match the expected format, a PAN must be exactly 10 characters in the correct pattern, invoice amounts must be non-negative. Invalid data is rejected before it ever reaches the business logic layer.
Data Encryption
Data is encrypted at rest using Azure's built-in encryption (AES-256) and in transit using TLS. Sensitive fields like API keys and connection strings are stored in Azure Key Vault, not in configuration files or environment variables that could be accidentally exposed.
Code Quality Practices
Technology choices are only half the story. How you write code matters just as much as what you write it in.
Code Reviews
Every change to the CAPilot codebase goes through a pull request. No exceptions. Even the founder's code gets reviewed. This is not about gatekeeping — it is about catching bugs, sharing knowledge, and maintaining consistency. Our PR template includes sections for: what changed, why it changed, how to test it, and any deployment considerations.
Testing Strategy
We use a pragmatic testing approach:
- Unit tests for business logic — invoice calculations, compliance date computations, role permission checks. These are fast and catch regressions immediately.
- Integration tests for API endpoints — we spin up an in-memory database, call the API, and verify the response. These ensure that validation, authentication, and database interactions work together correctly.
- Manual testing for UI flows — while we are building out automated E2E tests, critical user journeys (login, client creation, invoice generation) are manually tested before each release.
Structured Logging with Serilog
Every API request, every database query, every error is logged in a structured format using Serilog. Logs include correlation IDs so we can trace a single user action across multiple services. When something goes wrong in production, we can search logs by user, by endpoint, by error type, and reconstruct exactly what happened.
Monitoring and Observability
You cannot fix what you cannot see. We use Azure Application Insights for real-time monitoring of both the frontend and backend:
- Request metrics — response times, error rates, throughput, and availability, all broken down by endpoint.
- Exception tracking — every unhandled exception is captured with full stack traces, request context, and user information (anonymised where appropriate).
- Custom metrics — we track business-level events like invoice generation count, task completion rates, and login frequency. This helps us understand not just whether the system is healthy, but whether users are getting value.
- Alerts — if the error rate spikes, if response times exceed thresholds, or if the database approaches capacity, we get notified immediately via email and Teams.
How This Stack Enables Rapid Iteration
The real test of a tech stack is not how it looks on paper — it is how fast you can ship. Here is what our stack enables in practice:
- A new API endpoint, from writing code to production, takes under two hours including code review.
- A new frontend feature, from design to deployment, typically takes one to three days depending on complexity.
- Database schema changes are handled through EF Core migrations and deployed automatically — no manual SQL scripts.
- Rolling back a bad deployment takes under 60 seconds using Azure deployment slots.
- Onboarding a new developer to the point where they can make their first PR takes about two days.
This speed is not about cutting corners. It is about choosing tools that work well together, automating everything that can be automated, and spending engineering time on product features instead of infrastructure plumbing.
What We Would Do Differently
No tech stack is perfect, and honesty matters. Here are a few things we have learned:
- We should have set up E2E tests earlier. Manual testing works when you have three pages. When you have thirty, it does not scale. We are now building out Playwright-based E2E tests, but we wish we had started sooner.
- Azure Static Web Apps has limitations. It is excellent for most things, but custom redirect rules and advanced routing can be fiddly. For our use case, it is still the right choice, but we have had to work around a few edge cases.
- We underestimated the importance of design tokens. Our CSS was initially somewhat ad-hoc. We have since moved to a proper design system with CSS custom properties, which has dramatically improved consistency.
These are solvable problems, and we are solving them. The core architecture — React, .NET, Azure, SQL Server — has proven itself solid.
Wrapping Up
Technology choices are not about following trends or impressing other engineers. They are about building a foundation that lets you serve your users reliably, ship features quickly, and scale when growth demands it.
For CAPilot, React + TypeScript gives us a fast, type-safe frontend. .NET 8 gives us a performant, enterprise-ready backend. Azure gives us managed infrastructure that a small team can operate confidently. And GitHub Actions ties it all together with automated testing and deployment.
If you are building a SaaS product and evaluating your own stack, I hope this gives you a useful reference point. And if you are a CA firm wondering what is under the hood of the software you are trusting with your practice data — now you know.