Server-Side MVC

How MVC works when the server renders complete HTML pages

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

  1. Browser sends request — user clicks a link or submits a form, sending an HTTP request (e.g., GET /users)
  2. Router dispatches to Controller — the routing layer matches the URL pattern and calls the appropriate controller method
  3. Controller asks Model for data — the controller calls model methods like User.getAll() or User.findById(id)
  4. Model queries database — the model executes SQL queries, processes results, and returns structured data to the controller
  5. Controller passes data to View — the controller selects the appropriate template and passes the model data to it
  6. 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