Client-side vs Server-side JavaScript
Client-side vs Server-side JavaScript
JavaScript started as a browser-only language. For its first 14 years, it could only run inside a web browser — on the client side. That changed in 2009 when Node.js was released, making JavaScript capable of running on servers too.
Today, JavaScript is the only language that runs natively on both sides of the web. Understanding the difference between client-side and server-side JS is fundamental to web development.
The Core Difference
Client-side JavaScript runs in the user's browser — on their device. It controls what the user sees and interacts with.
Server-side JavaScript runs on a remote server — in a data center somewhere. It handles databases, business logic, authentication, and sends responses to browsers.
Client-side
Runs in the browser on the user's machine. Controls the UI, responds to clicks, updates the page without reload.
Server-side
Runs on a remote server. Handles databases, authentication, file storage, and business logic.
Full-stack
JavaScript on both sides. One language, one team, shared code — the unique advantage JS has over every other language.
Universal / Isomorphic
Code that runs on both client and server. Next.js and Nuxt are built on this concept.
Client-side JavaScript
Client-side JavaScript
Client-side JavaScript is code that runs in the user's web browser. When a browser loads a webpage, it downloads the HTML, CSS, and JavaScript files. The JavaScript runs locally on the user's device — not on any server.
Where it runs
Client-side JS runs inside the browser's JavaScript engine:
- Chrome → V8
- Firefox → SpiderMonkey
- Safari → JavaScriptCore
The code executes on the user's CPU using the user's memory. The server has nothing to do with it after sending the files.
What client-side JavaScript can do:
Manipulate the DOM
Add, remove, or change HTML elements and CSS styles dynamically without reloading the page.
Handle User Events
Respond to clicks, keyboard input, mouse movement, form submissions, and scroll events.
Make API Requests
Fetch data from servers using fetch() or XMLHttpRequest — load new content without a full page reload.
Use Browser Storage
Read and write localStorage, sessionStorage, IndexedDB, and cookies on the user's device.
Animations & Canvas
Create animations with CSS transitions, Web Animations API, Canvas 2D, and WebGL.
Access Device APIs
Geolocation, camera, microphone, notifications, vibration, and clipboard — with user permission.
What client-side JavaScript CANNOT do:
Browser Security Restrictions
For security reasons, client-side JavaScript is sandboxed — it cannot:
- Read files on the user's computer (without explicit user selection)
- Access other browser tabs or windows from a different origin
- Connect directly to a database (no direct MySQL/MongoDB connection)
- Send emails or make server-to-server requests hidden from the user
- Run in the background when the browser tab is closed
- Access the user's OS beyond what the browser explicitly exposes
1<!DOCTYPE html>2<html>3<head>4 <style>5 body { font-family: sans-serif; padding: 20px; background: #0f172a; color: white; }6 .box { padding: 20px; background: #1e293b; border-radius: 10px; margin: 10px 0; }7 button { padding: 10px 20px; margin: 5px; border: none; border-radius: 8px; cursor: pointer; font-weight: bold; }8 #output { color: #4ade80; font-size: 18px; margin: 10px 0; min-height: 30px; }9 #storage-display { color: #94a3b8; font-size: 13px; }10 </style>11</head>12<body>13 <div class="box">14 <h3>🎨 DOM Manipulation</h3>15 <div id="output">Click a button!</div>16 <button onclick="changeText()" style="background:#6366f1">Change Text</button>17 <button onclick="changeColor()" style="background:#0891b2">Change Color</button>18 </div>1920 <div class="box">21 <h3>💾 localStorage (Client-side Storage)</h3>22 <input id="inp" placeholder="Type something..." style="padding:8px;border-radius:6px;border:none;background:#334155;color:white;margin-right:8px">23 <button onclick="saveData()" style="background:#16a34a">Save to Browser</button>24 <button onclick="loadData()" style="background:#9333ea">Load from Browser</button>25 <div id="storage-display">Stored: (nothing yet)</div>26 </div>2728 <script>29 // DOM manipulation30 const messages = ['Hello, Browser!', 'Running on YOUR device!', 'No server needed!', 'Pure client-side JS!'];31 let msgIndex = 0;32 function changeText() {33 document.getElementById('output').textContent = messages[msgIndex++ % messages.length];34 }35 function changeColor() {36 const colors = ['#4ade80','#60a5fa','#f472b6','#fb923c','#a78bfa'];37 document.getElementById('output').style.color = colors[Math.floor(Math.random()*colors.length)];38 }3940 // localStorage41 function saveData() {42 const value = document.getElementById('inp').value;43 localStorage.setItem('myData', value);44 document.getElementById('storage-display').textContent = `Saved: "${value}" (reload page — it persists!)`;45 }46 function loadData() {47 const value = localStorage.getItem('myData') || '(nothing saved yet)';48 document.getElementById('storage-display').textContent = `Loaded from storage: "${value}"`;49 }50 </script>51</body>52</html>
Server-side JavaScript
Server-side JavaScript
Server-side JavaScript runs on a remote computer (the server) — not in the browser. The most common runtime is Node.js, which uses the V8 engine but replaces browser APIs with server-focused APIs like file system access, networking, and process management.
What changed in 2009
Before Node.js (2009), if you wanted server-side code you needed Python, PHP, Ruby, Java, or C#. You had to know two completely different languages for frontend and backend. Node.js let JavaScript developers write the entire application in one language — client and server.
What server-side JavaScript can do:
Database Access
Read and write to databases (MongoDB, PostgreSQL, MySQL, Redis) directly — not possible in browsers.
File System
Read, write, delete files on the server's disk. Process uploads, generate reports, manage logs.
Authentication & Secrets
Store API keys, passwords, tokens safely. Run authentication logic without exposing secrets to users.
Email & Notifications
Send emails via SMTP, push notifications, SMS, webhooks — server-to-server communication.
Business Logic
Process payments, calculate pricing, enforce rules, validate data before saving — trusted execution.
Background Tasks
Run cron jobs, process queues, handle webhooks, and perform long operations independent of any browser.
1// Node.js — runs on the SERVER, not in a browser2const fs = require('fs'); // File system — NOT available in browsers3const http = require('http'); // HTTP server — NOT available in browsers45// ✅ Read a file from disk6const content = fs.readFileSync('./data/users.json', 'utf8');7const users = JSON.parse(content);8console.log(`Loaded ${users.length} users from disk`);910// ✅ Create an HTTP server11const server = http.createServer((req, res) => {12 if (req.url === '/api/users' && req.method === 'GET') {13 // Send JSON response to the browser14 res.writeHead(200, { 'Content-Type': 'application/json' });15 res.end(JSON.stringify(users));16 } else {17 res.writeHead(404);18 res.end('Not found');19 }20});2122server.listen(3000, () => {23 console.log('Server running at http://localhost:3000');24});2526// ✅ Connect to a database (MongoDB example)27const { MongoClient } = require('mongodb');28async function getUsers() {29 const client = new MongoClient('mongodb://localhost:27017');30 await client.connect();31 const db = client.db('myapp');32 const users = await db.collection('users').find({}).toArray();33 console.log(users);34 await client.close();35}36getUsers();
Never expose secrets in client-side code
Everything in client-side JavaScript is visible to the user — they can open DevTools and read your code. Never put in browser code:
- Database passwords or connection strings
- Private API keys or secret tokens
- Payment processing secret keys
- Admin credentials
These belong on the server only. The browser should only receive public API keys (like Google Maps public key) that are designed to be exposed.
Side-by-Side Comparison
Side-by-Side Comparison
| Client-side JavaScript | Server-side JavaScript |
|---|---|
| Where it runs: User's browser / device Runtime: V8, SpiderMonkey, JSCore (built into the browser) APIs available: DOM, BOM, Web APIs fetch, localStorage Canvas, WebGL, WebRTC Geolocation, Camera Can access: ✅ The webpage / DOM ✅ Browser storage ✅ User's device APIs ❌ Database directly ❌ Server file system ❌ Private API keys Security: Code is PUBLIC — user can read all of it Performance limit: User's device hardware | Where it runs: Remote server (data center) Runtime: Node.js, Deno, Bun (standalone runtimes) APIs available: fs, http, crypto process, Buffer, Stream child_process, cluster OS-level networking Can access: ✅ Database directly ✅ File system ✅ Private secrets/keys ✅ Other servers ✅ Background tasks ❌ User's browser DOM ❌ User's local storage Security: Code is PRIVATE — user never sees it Performance limit: Server hardware (scalable) |
| Feature | Client-side | Server-side |
|---|---|---|
| Where it runs | User's browser | Remote server |
| Runtime | Browser (V8, SpiderMonkey) | Node.js / Deno / Bun |
| DOM access | ✅ Full access | ❌ No DOM |
| Database access | ❌ Never directly | ✅ Full access |
| File system | ❌ Restricted | ✅ Full access |
| Code visibility | ❌ Fully visible to user | ✅ Hidden from user |
| Secrets/API keys | ❌ Never safe here | ✅ Safe here |
| localStorage | ✅ Read/write | ❌ Not available |
| HTTP requests | ✅ fetch(), XHR | ✅ http module, fetch |
| Runs without browser | ❌ Needs a browser | ✅ Runs standalone |
| Scales with users | ✅ Each user runs own JS | ⚠️ Must scale server |
| Performance cost | User's CPU/RAM | Server CPU/RAM |
How Client and Server Work Together
How Client and Server Work Together
In a real web application, client-side and server-side JavaScript work together in a request-response cycle. The browser never talks directly to a database — it always goes through the server.
1. User opens the browser
The user navigates to https://myapp.com. The browser sends an HTTP GET request to the server.
2. Server sends HTML, CSS, JS files
The Node.js server responds with the HTML page and links to CSS and JavaScript files. The browser downloads and runs them.
3. Client-side JS runs in the browser
The downloaded JavaScript runs in V8 (in the browser). It renders the UI, sets up event listeners, and may show a loading state.
4. Client-side JS calls the API
The browser JS makes a fetch('/api/products') request to the server to load data. This is an HTTP request — not a direct database connection.
1// Client-side — running in the browser2async function loadProducts() {3 const response = await fetch('/api/products'); // HTTP request to server4 const products = await response.json();5 renderProducts(products); // Update the DOM6}7loadProducts();
5. Server-side JS handles the request
The Node.js server receives the request, queries the database, applies business logic, and sends back JSON data. The database password never leaves the server.
1// Server-side — running in Node.js (user never sees this)2app.get('/api/products', async (req, res) => {3 const products = await db.query(4 'SELECT * FROM products WHERE active = true'5 // Database credentials are safe on the server6 );7 res.json(products); // Send JSON to the browser8});
6. Client renders the response
The browser JS receives the JSON, updates the DOM to display the products. The user sees the updated page — no full reload required.
The API Layer is the Bridge
The server exposes a REST API (or GraphQL API) that the client consumes. The client sends HTTP requests, the server responds with JSON. This clean separation means:
- The client never knows about the database
- The server never directly touches the user's browser
- Either side can be rewritten independently
- The same API can serve web, mobile, and desktop clients
Server-side Runtimes
Server-side JavaScript Runtimes
Multiple runtimes let you run JavaScript on a server. Each has different strengths, APIs, and design philosophies.
| Runtime | Engine | Created By | Year | Key Strength |
|---|---|---|---|---|
| Node.js | V8 | Ryan Dahl | 2009 | Largest ecosystem (npm). Battle-tested. Widest hosting support. |
| Deno | V8 | Ryan Dahl | 2018 | Secure by default. TypeScript built-in. Web-standard APIs. No node_modules. |
| Bun | JavaScriptCore | Jarred Sumner | 2022 | Extremely fast. Built-in bundler, test runner, package manager. |
| Cloudflare Workers | V8 | Cloudflare | 2017 | Runs at the edge (150+ locations). Near-zero cold start. Service Worker API. |
| AWS Lambda (Node) | V8 | Amazon | 2014 | Serverless. Pay per invocation. Scales to zero automatically. |
Frameworks for Each Side
Popular Frameworks for Each Side
- 1React
- 2Vue
- 3Angular
- 4Svelte
- 5Solid
- 6Preact
- 1Express
- 2Fastify
- 3NestJS
- 4Hono
- 5Koa
- 6AdonisJS
- 1Next.js
- 2Nuxt
- 3SvelteKit
- 4Remix
- 5Astro
- 6SolidStart
Full-stack frameworks run code in both places
Frameworks like Next.js let you write client and server code in the same project, sometimes in the same file. A Next.js page.tsx can have a getServerSideProps function that runs on the server (safe to access the DB) and a React component that runs in the browser (updates the DOM). The framework handles the boundary between the two.
Same Task — Client vs Server
Same Task — Two Different Approaches
Let's see the same goal achieved on the client side vs the server side to understand their differences clearly.
// Runs in the BROWSER// Gets data from the server, updates DOMasync function loadUserProfile(userId) {// Can't query DB directly from browser// Must go through the server APIconst res = await fetch(`/api/users/${userId}`);if (!res.ok) throw new Error('User not found');const user = await res.json();// Update the DOM with received datadocument.getElementById('name').textContent = user.name;document.getElementById('email').textContent = user.email;document.getElementById('avatar').src = user.avatarUrl;}// Called when the page loadsloadUserProfile(42);
// Runs in NODE.JS on the SERVER// Queries DB directly, sends responseconst express = require('express');const db = require('./database'); // private DB connectionconst app = express();app.get('/api/users/:id', async (req, res) => {const { id } = req.params;// Can query database directly — safe hereconst user = await db.query('SELECT id, name, email, avatar_url FROM users WHERE id = $1',[id] // parameterized — prevents SQL injection);if (!user) {return res.status(404).json({ error: 'Not found' });}// Send data to browser as JSONres.json(user);});app.listen(3000);
// Runs in the BROWSER// Instant feedback — no server round-trip neededconst form = document.querySelector('#signup-form');form.addEventListener('submit', (e) => {e.preventDefault();const email = form.email.value.trim();const password = form.password.value;// Instant client-side validationif (!email.includes('@')) {showError('Invalid email address');return;}if (password.length < 8) {showError('Password must be 8+ characters');return;}// All good — send to serversubmitToServer({ email, password });});
// Runs in NODE.JS on the SERVER// ALWAYS validate on server — client can be bypassed!app.post('/api/signup', async (req, res) => {const { email, password } = req.body;// Server MUST re-validate — client code can be// disabled or tampered with by any userif (!email || !email.includes('@')) {return res.status(400).json({ error: 'Invalid email' });}if (!password || password.length < 8) {return res.status(400).json({ error: 'Password too short' });}// Check if email already exists (requires DB — only possible here)const existing = await db.findUserByEmail(email);if (existing) {return res.status(409).json({ error: 'Email already in use' });}// Hash password before storing (bcrypt — never store plain text)const hash = await bcrypt.hash(password, 12);await db.createUser({ email, passwordHash: hash });res.status(201).json({ message: 'Account created' });});
Never rely only on client-side validation
Client-side validation improves UX (instant feedback, no round-trip), but it can be completely bypassed — a user can open DevTools, disable JavaScript, or send raw HTTP requests with invalid data. Always validate on the server too. Client validation = good UX. Server validation = actual security.
Rendering Strategies
Rendering Strategies — Where HTML is Generated
A key decision in modern web development is where the HTML for a page is generated — on the server, in the browser, or both. This decision affects performance, SEO, and user experience.
| Strategy | Where HTML is Built | First Load | SEO | Use Case |
|---|---|---|---|---|
| CSR — Client-Side Rendering | Browser (JS builds the DOM) | Slow (blank page first) | Poor (bots see empty page) | Dashboards, apps behind login |
| SSR — Server-Side Rendering | Server (on every request) | Fast (full HTML sent) | Great (full HTML for bots) | News sites, e-commerce, blogs |
| SSG — Static Site Generation | Server (at build time) | Fastest (pre-built HTML) | Great | Marketing pages, documentation |
| ISR — Incremental Static Regeneration | Server (on demand, then cached) | Fast | Great | E-commerce with frequent updates |
| Streaming SSR | Server (sent in chunks) | Very fast (progressive) | Great | Large pages with slow data fetches |
Test Your Knowledge
Test Your Knowledge
Which of these tasks MUST be done on the server side?
A developer puts their database password in client-side JavaScript. What is the risk?
What is the main role of Node.js?
In a typical web app, how does client-side JavaScript get data from a database?
What does 'CSR' stand for and what is its main drawback?
Key Takeaways
Key Takeaways
- 1Client-side JS runs in the browser on the user's device. Server-side JS runs on a remote server. Same language, completely different environments.
- 2Everything in client-side code is visible to the user — never put secrets, passwords, or private keys in browser JavaScript.
- 3Always validate user input on the server, even if you also validate on the client. Client-side validation can be bypassed.
- 4Browsers cannot connect to databases directly. Always use a server API as the middleman.
- 5Node.js, Deno, and Bun let JavaScript run on servers. Node.js has the largest ecosystem and is the most widely deployed.
- 6Full-stack frameworks (Next.js, Nuxt, SvelteKit) let you write client and server code in one project, sometimes in the same file.
- 7Choose your rendering strategy (CSR, SSR, SSG) based on your SEO needs, content update frequency, and performance requirements.
- 8JavaScript being usable on both sides is unique — every other popular language requires learning a separate frontend language (HTML/JS) anyway.
JavaScript Engine Overview
Understand how the JS engine parses, compiles, and runs your code under the hood.
Variables & Data Types
Start writing JavaScript — the fundamentals that work on both client and server.
fetch() & APIs
Learn how client-side JS communicates with servers using the Fetch API.
Node.js Basics
Get started with server-side JavaScript using Node.js and Express.
You now understand where JavaScript runs!
Client-side handles what users see and interact with. Server-side handles data, security, and business logic. Together they form the full-stack web — and JavaScript is the only language that spans both sides natively.
Try it in the Javascript Compiler
Run and experiment with Javascript code right in your browser — no setup needed.