Server vs Client Execution

Understanding where your code runs

← Back to Index

The Fundamental Question: Where Does Code Run?

One of the most important concepts in web development is understanding where your code executes. This isn't just a technical detail—it has profound implications for security, performance, user experience, and how you architect your entire application.

When you write a web application, you're actually writing code that runs in two completely different environments:

This distinction affects everything from what languages you can use, to what data you can access, to how secure your application can be.

A Helpful Analogy: Movie Production

Think of it like making and showing a movie:

In web development, code executes in two distinct environments:

  • Server-side (Backend): Code runs on YOUR servers. You control the environment completely. Languages: Java, Python, Node.js, Go, C#, Ruby, etc.
  • Client-side (Frontend): Code runs in the USER'S browser. You send code to them, but they control the environment. Languages: JavaScript (primarily), plus HTML and CSS for structure and styling.

Why This Matters

Understanding this distinction is crucial because:

Visual Overview: The Complete Picture

// Visual overview:

┌─────────────────────────────────────────────────────────────────┐
│                         YOUR SERVER                              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Java / Spring Boot                                      │    │
│  │  - Business logic                                        │    │
│  │  - Database access                                       │    │
│  │  - Authentication                                        │    │
│  │  - API endpoints                                         │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘
                              │
                         HTTP Response
                      (HTML, JSON, etc.)
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                      USER'S BROWSER                              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  JavaScript / HTML / CSS                                 │    │
│  │  - UI rendering                                          │    │
│  │  - User interactions                                     │    │
│  │  - Form validation                                       │    │
│  │  - Animations                                            │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘

Server-Side Execution (Backend)

// Server-side code runs on YOUR infrastructure
// Language: Java, Python, C#, Node.js, Go, etc.

@RestController
public class UserController {

    @Autowired
    private UserRepository userRepository;  // Database access

    @Autowired
    private PasswordEncoder encoder;  // Security

    @PostMapping("/api/register")
    public User register(@RequestBody RegisterRequest request) {
        // ALL of this runs on the SERVER:

        // 1. Validate data (server-side validation is REQUIRED)
        if (userRepository.existsByEmail(request.getEmail())) {
            throw new EmailExistsException();
        }

        // 2. Hash password (NEVER trust client with security)
        String hashedPassword = encoder.encode(request.getPassword());

        // 3. Save to database (client can't access DB directly)
        User user = new User();
        user.setEmail(request.getEmail());
        user.setPassword(hashedPassword);

        return userRepository.save(user);
    }
}

What Runs on the Server

Task Why Server?
Database operations Security - clients can't access DB directly
Authentication Security - passwords, tokens, sessions
Business logic Consistency - same rules for all clients
Data validation Security - client validation can be bypassed
File processing Performance - heavy computation
Third-party API calls Security - hide API keys
Email sending Security - SMTP credentials

Client-Side Execution (Frontend)

// Client-side code runs in the USER'S BROWSER
// Language: JavaScript (or TypeScript compiled to JS)

// React/JavaScript example
function RegistrationForm() {
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [error, setError] = useState('');

    // ALL of this runs in the BROWSER:

    // 1. Handle user input
    const handleEmailChange = (e) => {
        setEmail(e.target.value);
    };

    // 2. Client-side validation (for UX, not security!)
    const validateForm = () => {
        if (!email.includes('@')) {
            setError('Invalid email');
            return false;
        }
        return true;
    };

    // 3. Submit to server
    const handleSubmit = async (e) => {
        e.preventDefault();
        if (!validateForm()) return;

        // Send data to server for REAL processing
        const response = await fetch('/api/register', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ email, password })
        });
    };

    // 4. Render UI
    return (
        <form onSubmit={handleSubmit}>
            <input value={email} onChange={handleEmailChange} />
            <button type="submit">Register</button>
        </form>
    );
}

What Runs on the Client

Task Why Client?
UI rendering User sees it - must be in browser
User interactions Immediate response - no round trip
Form validation (UX) Instant feedback - better experience
Animations Smooth - no network latency
Local storage Offline capability, preferences
Single Page App routing Fast navigation without reload

The Complete Picture

// Example: User submits a login form

// ═══════════════════════════════════════════════════════════════
// STEP 1: CLIENT - User fills form (Browser/JavaScript)
// ═══════════════════════════════════════════════════════════════

// JavaScript in browser
const form = document.getElementById('loginForm');
form.addEventListener('submit', async (e) => {
    e.preventDefault();

    // Client-side validation (instant feedback)
    const email = document.getElementById('email').value;
    if (!email) {
        showError('Email required');  // Immediate, no server needed
        return;
    }

    // Send to server
    const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify({ email, password })
    });
});

// ═══════════════════════════════════════════════════════════════
// STEP 2: SERVER - Process login (Your Server/Java)
// ═══════════════════════════════════════════════════════════════

@PostMapping("/api/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {

    // Server-side validation (REQUIRED - client can be bypassed)
    if (request.getEmail() == null) {
        return ResponseEntity.badRequest().body("Email required");
    }

    // Database lookup (only server can do this)
    User user = userRepository.findByEmail(request.getEmail());
    if (user == null) {
        return ResponseEntity.status(401).body("Invalid credentials");
    }

    // Password verification (server has the hash)
    if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
        return ResponseEntity.status(401).body("Invalid credentials");
    }

    // Generate JWT token (server secret key)
    String token = jwtService.generateToken(user);

    return ResponseEntity.ok(new LoginResponse(token));
}

// ═══════════════════════════════════════════════════════════════
// STEP 3: CLIENT - Handle response (Browser/JavaScript)
// ═══════════════════════════════════════════════════════════════

// Back in the browser
if (response.ok) {
    const { token } = await response.json();
    localStorage.setItem('token', token);  // Store locally
    window.location.href = '/dashboard';   // Navigate
} else {
    showError('Login failed');  // Update UI
}

Security: Never Trust the Client

Critical Security Rule

NEVER trust data from the client. Everything can be manipulated!

// ❌ BAD: Trusting client-side validation only

// Client (JavaScript) - can be bypassed!
if (age >= 18) {
    submitOrder();  // User can remove this check in browser DevTools
}

// Server (Java) - no validation
@PostMapping("/order")
public void createOrder(@RequestBody Order order) {
    orderRepository.save(order);  // 💀 Saves anything!
}

// ═══════════════════════════════════════════════════════════════

// ✅ GOOD: Server-side validation (always!)

// Client (JavaScript) - for UX only
if (age >= 18) {
    submitOrder();  // Nice UX, but not security
}

// Server (Java) - THE REAL CHECK
@PostMapping("/order")
public void createOrder(@RequestBody Order order) {
    // Validate EVERYTHING on server
    if (order.getUser().getAge() < 18) {
        throw new ValidationException("Must be 18+");
    }
    if (order.getQuantity() <= 0) {
        throw new ValidationException("Invalid quantity");
    }
    orderRepository.save(order);
}

What Can Be Manipulated on Client

// EVERYTHING the client sends can be forged:

// 1. Form data
//    User can change hidden fields, prices, quantities

// 2. JavaScript validation
//    Can be disabled in browser DevTools

// 3. HTTP headers
//    Can be modified with curl or Postman

// 4. Cookies (if not HttpOnly)
//    Can be read/modified via JavaScript

// 5. Local storage
//    User has full access

// Example attack:
curl -X POST https://shop.com/api/order \
  -H "Content-Type: application/json" \
  -d '{"productId": 1, "price": 0.01}'  # Changed price!

// Server MUST verify price from database, not trust the request

Server-Side Rendering vs Client-Side Rendering

Server-Side Rendering (SSR)

// Server generates complete HTML

// Thymeleaf (Java template engine)
@GetMapping("/products")
public String productList(Model model) {
    List<Product> products = productService.findAll();
    model.addAttribute("products", products);
    return "products";  // Renders products.html template
}

<!-- products.html -->
<html>
<body>
    <h1>Products</h1>
    <ul>
        <!-- Server fills this in BEFORE sending to browser -->
        <li th:each="product : ${products}">
            <span th:text="${product.name}">Product Name</span>
        </li>
    </ul>
</body>
</html>

// Browser receives COMPLETE HTML - no JavaScript needed

Client-Side Rendering (CSR)

// Server sends minimal HTML + JavaScript
// JavaScript fetches data and builds UI

// Server (Java) - just an API
@GetMapping("/api/products")
public List<Product> getProducts() {
    return productService.findAll();  // Returns JSON
}

// Client (React/JavaScript) - builds the UI
function ProductList() {
    const [products, setProducts] = useState([]);

    useEffect(() => {
        fetch('/api/products')
            .then(res => res.json())
            .then(data => setProducts(data));
    }, []);

    return (
        <ul>
            {products.map(p => <li key={p.id}>{p.name}</li>)}
        </ul>
    );
}
Aspect Server-Side Rendering Client-Side Rendering
Initial load Faster (HTML ready) Slower (wait for JS)
SEO Better (content visible) Needs extra work
Interactivity Page reloads Smooth SPA experience
Server load Higher (renders HTML) Lower (just API)
Examples Thymeleaf, JSP React, Vue, Angular

Modern Hybrid Approaches

// Modern apps often combine both approaches

// 1. Next.js (React) - SSR + CSR
//    - Server renders initial page (fast, SEO)
//    - Client takes over for interactivity

// 2. Spring Boot + Thymeleaf + HTMX
//    - Server renders HTML fragments
//    - HTMX swaps them without full page reload

@GetMapping("/products/search")
public String searchProducts(
        @RequestParam String query,
        Model model) {
    model.addAttribute("products", productService.search(query));
    return "fragments/product-list";  // Just the list HTML
}

<!-- HTML with HTMX -->
<input type="search"
       hx-get="/products/search"
       hx-trigger="keyup changed delay:300ms"
       hx-target="#results">

<div id="results">
    <!-- Server-rendered HTML inserted here -->
</div>

Quick Reference

Server-Side (Java)

  • Database operations
  • Authentication & authorization
  • Business logic & rules
  • Data validation (REQUIRED)
  • API keys & secrets
  • Heavy computations
  • Email/SMS sending

Client-Side (JavaScript)

  • UI rendering & updates
  • User interactions
  • Form validation (for UX)
  • Animations & transitions
  • Local storage access
  • SPA navigation
  • Real-time updates (with WebSocket)

Summary

  • Server-side: Runs on your servers - Java, secure, database access
  • Client-side: Runs in browser - JavaScript, UI, interactions
  • Security: NEVER trust client data - always validate on server
  • SSR: Server generates HTML - fast initial load, good SEO
  • CSR: JavaScript builds UI - smooth SPA experience
  • Modern apps: Often combine both approaches