Model-View-Controller (MVC) Design Pattern
MVC is probably the first architecture pattern most developers encounter. It splits your application into three parts, each with a clear job. Sounds simpl...
23 Mar 2024

MVC is probably the first architecture pattern most developers encounter. It splits your application into three parts, each with a clear job. Sounds simple. The tricky part is knowing where to draw the lines.
The Three Parts
Model — Your data and business logic. It stores state, validates input, and runs calculations. The Model doesn't know anything about how the data is displayed. It doesn't know there's a UI at all.
class TaskModel {
constructor() {
this.tasks = [];
this.listeners = [];
}
addTask(task) {
this.tasks.push(task);
this.notify();
}
onUpdate(listener) {
this.listeners.push(listener);
}
notify() {
this.listeners.forEach(fn => fn(this.tasks));
}
}
View — The UI layer. It renders data from the Model and captures user input. The View doesn't contain business logic. It just displays what it's told to display.
class TaskView {
static renderTasks(tasks) {
const taskList = document.getElementById('task-list');
taskList.innerHTML = '';
tasks.forEach(task => {
const li = document.createElement('li');
li.textContent = task;
taskList.appendChild(li);
});
}
}
Controller — The glue. It receives user input from the View, processes it (or delegates to the Model), and updates the Model. The Model then notifies the View to re-render.
class TaskController {
constructor(model) {
this.model = model;
this.model.onUpdate(tasks => TaskView.renderTasks(tasks));
}
addTask(task) {
if (!task.trim()) return;
this.model.addTask(task);
}
}
The flow: User action -> Controller -> Model -> View. Data flows in one direction. Each piece has one job.
Why MVC Still Matters
Even if you're not writing vanilla JS apps, MVC's principles show up everywhere. Express routes are controllers. React components are views. Redux stores are models. Understanding MVC makes every framework easier to learn.
Where It Gets Messy
- Fat controllers. Business logic leaks into controllers because it's the easiest place to add code. Fight this by keeping controllers thin — they should delegate to the Model or a service layer.
- View-Model coupling. When the View directly reads from the Model (instead of through the Controller), you get tight coupling. Any change to the Model's shape breaks the View.
- MVC variants. MVC, MVP, MVVM — they're all variations on the same idea. Don't get hung up on which variant is "correct." The principle matters: separate your data, your display, and your coordination logic.
The benefit: Clean separation of concerns. Easy to test — you can unit test the Model without a DOM, test the Controller without rendering. Multiple views can share the same Model.
The cost: For small apps, MVC adds files and indirection that aren't justified. The boundaries can blur in practice, especially in frontend code where View and Controller often overlap. And strict MVC doesn't address cross-cutting concerns like auth or logging — you need additional patterns for those.
I use MVC's principles in almost every application I build, even if I don't follow it rigidly. The core lesson — separate your data from your display from your coordination — applies regardless of framework or language.
Keep reading
- Event Sourcing Checklist: When It Makes Sense & Common Pitfalls
- Error Boundary React Design Pattern
- Context API React Design Pattern
- Higher-Order Component (HOC) React Design Pattern
- Container Component React Design Pattern
- Best Practices for Structuring Express.js Applications with Prisma Design Pattern