We read earlier on how MVC marked a shift in engineering thinking and architectural patterns, coming from no structure at all while writing code to having a clear triad in the form of MVC while creating an application.
Although MVC as a concept at its birth was more of a server-heavy architecture with most of the processing load on the Controller and the View layer just managing user input and for displaying data on the screen.
When we're along these lines, let's know of 2 more things:
- Thin Client - As the name suggests, here the client is thin, or it is involved in a lot less workload than the server. These are the kind of architectures followed in traditional multi-page applications or static sites.
- Thick Client - This is just the reverse of the above, where significant data processing happens on the client side. For e.g.- Single Page Applications, offline-first applications etc.
But this was not a very prominent concept back in the day when applications were a lot less interactive. Modern web-apps follow a thick client architecture.
Let's see how a modern MVC web-app looks like
Modern MVC apps don't need a backend server, they can work purely on the frontend itself with Redux state management working as Model, Hooks as the Controller and React components as the View layer.
And extending on the same logic, there was the inception of HMVC (Hierarchical MVCs), where instead of having a single MVC structure across the system, we break down the app into nested modular MVC components.
Much like the modern-day structure:
But MVC weren't the last-stop solution. In-fact the concept gave rise to a lot of discussions and possible improvements. For the pros, they are easy to understand, develop and maintain.
But on the cons, with increasing complexity and sophistication in applications the boundary between the different layers start looking blurry.
Let's take the case of a fat controller. A fat controller is what the controller started turning into as the handling of events got passed down from the View. It has to send requests to the server, handle API responses, sync data with the backend and also do client-side caching.
And so comes the next architectural pattern, a change focusing on differentiating the UI and business logic entirely: MVVM(Model-View-ViewModel)
Let's understand the difference between the UI and business logic.
| UI/View Logic | Business logic |
|---|---|
| Closing a window | Changing the username |
| Disabling a button | Making a transaction |
| Updating the download status | Updating wishlist content |
How does MVVM differs between the View and Business logic?
The Model - consists of the domain data(e.g. User, Order, Invoice, + services like userApi, authService, etc.) and business rules (cart must have at least one item to checkout)
The View - actual UI the user sees, which in React us implemented by the JSX.
And here, instead of the Controller, which earlier used to handle requests and decide what happens next, is now replaced by the View Model.
View Model is the brain sitting between the View and the Model. It exposes state and actions is a View-friendly form.
How MVVM separates them
Views:
- Render JSX based on props and ViewModel state.
- Call ViewModel methods on user actions (onClick, onChange etc.).
- Contain only presentation logic (show/hide, format dates, mapping arrays to lists).
ViewModels:
- Own the screen state i.e. isLoading, error, selectedUser, etc.
- Wrap all business logic + async operations like loadUsers, saveUser, deleteUser.
- Transform raw models into a shape that’s easy for the View. e.g. from [{ firstName, lastName }] ➜ [{ fullName, initials }].
MVVM brings along with it a concept called 2-way binding. As the name suggests, it helps the data bind both ways.
When the ViewModel changes → the View updates automatically.
When the user changes something in the View → the ViewModel updates automatically.
This is not something that we'd directly get out-of-the-box in a library like React, but it can be implemented in this fashion:
1) ViewModel exposes value + setter
import { useState } from "react";
export function useProfileViewModel() {
const [name, setName] = useState("");
function updateName(newName: string) {
setName(newName); // updates ViewModel state (Model -> View)
}
return {
name,
updateName,
};
}
In MVVM:
name is the ViewModel state watched by the View.
updateName is the command that changes the state.
2) The View binds the UI element to the ViewModel
export function ProfilePage() {
const { name, updateName } = useProfileViewModel();
return (
<input
value={name} // ViewModel → View
onChange={(e) => updateName(e.target.value)} // View → ViewModel
/>
);
}
Multiple Views can share a single View Model but the vice-versa doesn't apply.
A single View cannot bring its data from multiple View Models!
In the early frontend architectures like the MVC days
- UI elements were manipulated directly
- Data was not bindable
- There was no place to store UI state (like loading, error, filters, selected item)
- The View was responsible for everything — DOM operations, formatting, logic, even data fetching
This meant the front-end was:
❌ Not reactive
❌ Hard to test
❌ Hard to scale
❌ Very brittle
There was no unified idea of “the UI is a function of state.”
MVVM is one of the first architectural patterns that formally brought the concept of state into frontend development.
Let's discuss some pros and cons:
| Pros | Cons |
|---|---|
| Clear layer separation between UI, Business and presentation logic | With higher modularisation of the application, the learning curve is a bit steeper |
| The higher decoupling makes it easier to scale and test | MVVM doesn't give you a place to organise all the other application concerns like API requests / data fetching logic, Caching, Authentication logic |
While MVVM wasn’t the final stop in architectural evolution, it paved the way for more expressive patterns and a deeper understanding of how applications should be structured as they scale.
The story continues...



Top comments (0)