Best Practices & Security
Code that merely works is only the first step. Real projects are read and changed by other people, run on slow phones, and face visitors who may try to break them. A few habits make the difference between fragile code and code you can trust.
This lesson collects the most important habits from across the course into four groups: writing clear modern code, letting tools check and format it, keeping your users safe, and keeping pages fast.
Write modern, strict code
Use modules or strict mode. Modules run in strict mode automatically (see Modules). For an old-style classic script, put "use strict"; as its first line. Strict mode turns silent mistakes into errors you can see:
let total = 0;
function addToCart(price) {
totla = total + price; // typo: should be total
}
try {
addToCart(250);
} catch (error) {
console.log(`${error.name}: ${error.message}`);
}
console.log(0 == "", 0 === "");
console.log(null == undefined, null === undefined);ReferenceError: totla is not defined true false true false
Without strict mode, the typo would silently create a new global variable called totla, and the cart total would never change. You would spend a long time wondering why.
The last two lines show why you should always use ===. The loose == converts types before comparing, so 0 == "" is true. The strict === compares without converting, which is almost always what you mean.
A few more habits pay off every day:
constby default,letonly when the value must change, nevervar. It makes clear which values change. See Variables: let and const.- Meaningful names.
discountedPricebeatsdp; functions are verbs, such ascalculateTotal()andshowError(). - Small functions that do one job. If you need the word "and" to describe a function, consider splitting it.
- Return early. Check for bad input at the top and
return, instead of nesting the main logic deep insideifblocks. - Avoid global variables. Keep data inside modules and functions, and pass it in as arguments.
// Hard to follow: deep nesting, unclear names
function f(u) {
if (u) {
if (u.age >= 18) {
if (u.verified) {
return "ok";
}
}
}
return "no";
}
// Clear: a meaningful name and early returns
function canBookTicket(user) {
if (!user) return false;
if (user.age < 18) return false;
return user.verified;
}Let tools check and format your code
A linter reads your code without running it and warns about likely bugs: unused variables, variables that were never declared, == instead of ===, unreachable code. The standard JavaScript linter is ESLint. A formatter rewrites your code's layout (spacing, quotes, line breaks) into one consistent style, so nobody argues about it. The most popular is Prettier. Both run with Node.js, and both have editor extensions that check or format your file every time you save.
npm init @eslint/config@latest
npx eslint .
npm install --save-dev --save-exact prettier
npx prettier . --writeThe first command asks a few questions and creates a config file. Current ESLint versions use a file named eslint.config.js, which can be as small as this:
// eslint.config.js
import js from "@eslint/js";
import globals from "globals";
export default [
js.configs.recommended,
{
languageOptions: {
globals: globals.browser, // allow window, document and other browser globals
},
rules: {
eqeqeq: "error", // always require === and !==
},
},
];Use them together: Prettier decides how code looks, ESLint finds problems in what it does. On a team, the shared config files mean everyone's code follows the same rules.
Keep your users safe
Front-end code runs on the visitor's device, and anyone can read it, change it in DevTools or bypass it completely. These rules protect your users and your site:
- Never put untrusted text into
innerHTML. Anything from users, URLs, storage or APIs goes into the page withtextContentorcreateElement. Otherwise you open the door to cross-site scripting (XSS), as shown in Changing the DOM. If you truly must display user-written HTML, clean it with a well-maintained sanitiser library first. - Check URLs before using them as links. A link like
javascript:...runs code when clicked. Allow onlyhttp:andhttps:. - Validate input twice. Checks in the browser give quick, friendly feedback; the server must check everything again, because browser checks can be skipped. See Working with Forms.
- No secrets in front-end code. API keys, passwords and tokens in JavaScript or
localStoragecan be read by anyone. Keep them on the server. - Be careful with packages. Every library you install is code you trust. Install only what you need, keep it updated, and run
npm auditto check for known security problems.
This review form applies several of these rules at once:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Safe reviews</title>
</head>
<body>
<h1>Reviews for Kabir's Dhaba, Jaipur</h1>
<form id="review-form">
<p><label>Your review <input name="review" required maxlength="200"></label></p>
<p><label>Your website (optional) <input name="website"></label></p>
<button>Post</button>
</form>
<p id="error" role="alert"></p>
<ul id="reviews"></ul>
<script type="module">
const form = document.querySelector("#review-form");
const errorBox = document.querySelector("#error");
// Allow only real web links, never "javascript:" or other schemes
function safeLink(text) {
try {
const url = new URL(text);
return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
} catch {
return null; // not a valid absolute URL
}
}
form.addEventListener("submit", (event) => {
event.preventDefault();
const review = form.elements.review.value.trim();
const website = form.elements.website.value.trim();
if (review.length < 5) {
errorBox.textContent = "Please write at least 5 characters.";
return;
}
const link = website ? safeLink(website) : null;
if (website && !link) {
errorBox.textContent = "The website must start with http:// or https://";
return;
}
errorBox.textContent = "";
const li = document.createElement("li");
li.textContent = review; // text, never HTML
if (link) {
const a = document.createElement("a");
a.href = link;
a.textContent = " (website)";
a.rel = "nofollow noopener";
li.append(a);
}
document.querySelector("#reviews").append(li);
form.reset();
});
</script>
</body>
</html>Try posting <b>Great</b> dal! as a review and javascript:alert(1) as the website. The tags appear as plain text, and the dangerous link is refused. Websites can add one more layer of protection with a Content Security Policy, a server header that tells the browser which scripts it may run.
Keep pages fast
Most slow pages are slow for a handful of reasons:
- Load scripts without blocking. Use
type="module"ordeferso the HTML can be shown first (see Adding JavaScript to a Page). - Touch the DOM less. Build elements in memory and insert them in one go, and use event delegation instead of thousands of listeners (see Events).
- Calm down busy events. Debounce input and resize handlers (see Timers & Animation Frames).
- Load heavy code only when needed with dynamic
import(). - Do not block the main thread with long loops; split the work or move it to a Web Worker.
- Measure before you optimise. The Performance panel in DevTools shows where time really goes.
One less obvious trap: reading a layout value such as offsetWidth right after changing styles forces the browser to recalculate the layout immediately. Doing that inside a loop, read-write-read-write, can be very slow. Read everything first, then write:
const cards = document.querySelectorAll(".card");
// Slow: each read forces a fresh layout after the previous write
for (const card of cards) {
card.style.height = `${card.offsetWidth * 0.75}px`;
}
// Faster: all reads first, then all writes
const widths = [...cards].map((card) => card.offsetWidth);
cards.forEach((card, i) => {
card.style.height = `${widths[i] * 0.75}px`;
});Key points
- Use modules (or
"use strict"),constandlet,===, clear names and small functions with early returns. - Let ESLint find likely bugs and Prettier keep formatting consistent, ideally on every save.
- Show untrusted data with
textContent, allow onlyhttp:andhttps:links, and never useeval()or string timers. - Validate in the browser for friendliness and on the server for security; keep secrets off the front end.
- For speed: non-blocking scripts, fewer DOM changes, debounced events, code loaded on demand, and measuring first.
Exercise
Rewrite this function using the habits from this lesson: strict equality, const, a meaningful name, early returns and no innerHTML for the user's name.
function x(n, a) {
var r;
if (n != "") {
if (a == 18 || a > 18) {
r = "Welcome " + n;
} else {
r = "Too young";
}
} else {
r = "No name";
}
document.querySelector("#msg").innerHTML = r;
}Show solution
function showWelcome(name, age) {
const message = document.querySelector("#msg");
if (name === "") {
message.textContent = "No name";
return;
}
if (age < 18) {
message.textContent = "Too young";
return;
}
message.textContent = `Welcome ${name}`;
}Early returns remove the nesting, === avoids type conversion surprises, and textContent means a name like <img onerror=...> is shown as text instead of running.
Quick Quiz
1. Why does strict mode help catch bugs?
In strict mode, a typo like totla = 5 throws a ReferenceError instead of creating a global variable.
2. What is the difference between ESLint and Prettier?
A linter checks what code does, while a formatter only changes how it looks.
3. A user enters javascript:alert(1) as their website. What should your code do?
javascript: URLs run code when clicked, so allow only web links.
4. Why must the server validate form data even if JavaScript already checked it?
Anything in the browser is under the user's control, so the server must never trust it.
Learn JavaScript with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
