How Server-Side MVC Works
In server-side MVC, the server does all the work: it processes the request, fetches data, renders HTML, and sends a complete page to the browser. The browser's job is simply to display what it receives.
Server-Side MVC Flow:
+---------+ HTTP Request +--------------+ Query +-------------+
| | ------------------> | | -----------> | |
| Browser | | Controller | | Model | <--> Database
| | | | <----------- | |
| | Complete HTML | | Data | |
| | <------------------ | | | +-------------+
| | | v |
+---------+ | +--------+ |
| | View | |
| | (EJS, | |
| | PHP, | |
| | Pug) | |
| +--------+ |
+--------------+
Server
Step-by-Step Flow
- Browser sends request — user clicks a link or submits a form, sending an HTTP request (e.g.,
GET /users) - Router dispatches to Controller — the routing layer matches the URL pattern and calls the appropriate controller method
- Controller asks Model for data — the controller calls model methods like
User.getAll()orUser.findById(id) - Model queries database — the model executes SQL queries, processes results, and returns structured data to the controller
- Controller passes data to View — the controller selects the appropriate template and passes the model data to it
- View renders HTML — the template engine combines the data with HTML markup, producing a complete page that is sent back to the browser
Key characteristic: The browser receives complete HTML pages. There is no client-side rendering — the server does everything. When the user clicks a link, the browser makes a full page request and the server sends back a complete new page.
File Structure
Both Node.js and PHP follow the same organizational pattern. Each layer has a single, clear responsibility:
# Node.js with EJS
project/
+-- app.js # Entry point, sets up Express
+-- routes/
| +-- users.js # URL -> controller mapping
+-- controllers/
| +-- userController.js # Handles request/response logic
+-- models/
| +-- User.js # Data access & business rules
+-- views/
+-- users/
+-- index.ejs # List all users
+-- show.ejs # Single user detail
+-- form.ejs # Create/edit form
# PHP (manual MVC)
project/
+-- index.php # Front controller (routes all requests)
+-- .htaccess # Rewrites all URLs to index.php
+-- controllers/
| +-- UserController.php # Handles request/response logic
+-- models/
| +-- User.php # Data access & business rules
+-- views/
+-- users/
+-- index.php # List all users
+-- show.php # Single user detail
+-- form.php # Create/edit form
Form Handling: POST/Redirect/GET
When a user submits a form, the server processes the data and then redirects the browser to a new page instead of rendering a view directly. This is the POST/Redirect/GET (PRG) pattern:
Browser Server | | | POST /stories | | ----------------------> | Controller: validate, save to DB | | | 302 Redirect /stories | | <---------------------- | Redirect prevents duplicate POST on refresh | | | GET /stories | | ----------------------> | Controller: fetch all, render list | | | 200 OK (HTML page) | | <---------------------- | Browser shows the updated list
Without the redirect, if the user refreshes the page after creating a story, the browser would re-submit the POST request and create a duplicate. With PRG, a refresh simply re-fetches the list page (a safe GET request).
There is one important exception: when validation fails, the controller re-renders the form (not a redirect) with an error message and the previously entered data, so the user does not lose their input.
XSS Prevention in Templates
Template engines provide built-in protection against Cross-Site Scripting (XSS) attacks. When rendering user-provided data, the template engine escapes HTML characters so they display as text rather than being executed as code.
In EJS, the <%= %> tag automatically escapes HTML. If someone creates a story with the title <script>alert('xss')</script>, EJS renders it as harmless text. In PHP, you use htmlspecialchars() to achieve the same protection.
| Template Engine | Safe Output (Escaped) | Raw Output (Unescaped) |
|---|---|---|
| EJS | <%= value %> |
<%- value %> (for trusted HTML only) |
| PHP | <?= htmlspecialchars($value) ?> |
<?= $value ?> |
| Blade (Laravel) | {{ $value }} |
{!! $value !!} |
When to Use Server-Side MVC
- Content-heavy sites — blogs, documentation, e-commerce product pages
- SEO-critical applications — search engines can index the HTML directly
- Progressive enhancement — the app works without JavaScript enabled
- Simple CRUD applications — admin panels, internal tools, form-based workflows
- Fast initial load — no JavaScript bundle to download before seeing content
Server-Side MVC Frameworks
| Framework | Language | Template Engine |
|---|---|---|
| Express + EJS | Node.js | EJS, Pug, Handlebars |
| Laravel | PHP | Blade |
| Rails | Ruby | ERB, Haml |
| Django | Python | Django Templates, Jinja2 |