Le Do Nghiem

Le Do Nghiem

AI Engineer

About meBooksSnippetsBlog

© 2026 Le Do Nghiem. All rights reserved.

Contact |

Back to Blog

How I Build Scalable Web Apps

Le Do Nghiem
Le Do NghiemAI Engineer
2025-09-10 4 min read
Share

A stack sketch I keep reusing

"Scalable" gets thrown around in job posts and README files. When I say it, I mean: the architecture can grow without a rewrite — more traffic, more features, more team members — without everything touching everything else.

I am not building Netflix on day one. I am building clear boundaries so day one does not paint me into a corner.

This is the stack and shape I use most: .NET API, React frontend, PostgreSQL, Redis. Containers when it is time to deploy — see Docker on DigitalOcean.


The pieces and why they exist

PieceJob
.NET APIBusiness logic, auth, validation, data access
ReactUI, client state, forms
PostgreSQLSource of truth — relational data, transactions
RedisCache, sessions, rate limits, hot reads

What scalable does not mean on day one:

  • Microservices because you might need them someday
  • Kubernetes because Docker sounds professional
  • CQRS because a blog post said so

What it does mean:

  • Stateless API behind a load balancer when traffic grows
  • Cache layer you can turn on without rewriting queries
  • Dependencies injected so you can test and swap implementations — more in Dependency Injection in ASP.NET Core

API layer: thin controllers, fat services

I keep controllers dumb. They parse HTTP, call a service, return a result.

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly IUserService _userService;

    public UsersController(IUserService userService)
    {
        _userService = userService;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetUser(int id)
    {
        var user = await _userService.GetUserByIdAsync(id);
        if (user == null) return NotFound();
        return Ok(user);
    }
}

Why: Unit tests hit UserService without spinning up HTTP. When load grows, I optimize the service and data layer — not routing attributes.


Where Redis enters

What: Cache expensive reads, store session keys, throttle abusive clients.

Why: Postgres is correct; Redis is fast. Not every read needs to hit disk.

Pattern I use:

  • Cache-aside for user profiles and config blobs
  • TTL always set — stale cache is a bug you can tune; infinite cache is a ghost story

Red flag: Caching before you measure. I add Redis when p95 latency or DB CPU tells me to, not because the diagram looked empty.


Frontend: API contract, not database shape

React talks to REST (or GraphQL) — never directly to Postgres. DTOs on the API define what the UI gets.

Why: You can change schema without redeploying the SPA. Mobile clients can share the same API later.


Horizontal scaling (when you need it)

  1. Run multiple API instances behind a load balancer
  2. Keep API stateless — sessions in Redis, not in-memory
  3. Postgres scales up first; read replicas later if read-heavy
  4. Background jobs for email, reports, webhooks — do not block HTTP threads

I have not needed step 4 on every project. I still design so a queue could pick up work without rewriting business logic.


Early architecture mistakes

  • Putting business rules in controllers — painful to test, painful to reuse.
  • One giant DbContext used everywhere — lifetime bugs and slow queries hide in there. DI lifetimes matter; see the DI guide.
  • Calling it scalable with no metrics — without logs and basic APM, you are guessing.

Day one vs year one

Pick boundaries: API owns data and rules, React owns UX, Postgres owns truth, Redis owns speed and ephemeral state. Deploy with Docker when you are ready.

If you are on .NET, spend an afternoon on dependency injection — it is the glue that keeps this layout testable as the codebase grows.

Scalable is a direction, not a day-one checkbox. Build something small with clear seams. Measure. Then scale what hurts.

On this page

  • A stack sketch I keep reusing
  • The pieces and why they exist
  • API layer: thin controllers, fat services
  • Where Redis enters
  • Frontend: API contract, not database shape
  • Horizontal scaling (when you need it)
  • Early architecture mistakes
  • Day one vs year one
Share
Previous Post

Mastering TypeScript: Tips and Tricks

Next Post

Deploying Apps with Docker and DigitalOcean