JavaScript Code Formatting Best Practices
Learn how to write clean, consistent JavaScript with Prettier, ESLint, and style guides. Covers indentation, naming conventions, and team configuration.
By Belqassim Djafer · Published · Updated
Why Code Formatting Matters
Consistent formatting reduces cognitive load when reading code, eliminates pointless style debates in code reviews, and makes version diffs cleaner. A properly formatted codebase allows developers to focus on logic rather than cosmetics.
Code is read far more often than it is written. Readable, consistently styled code directly impacts productivity and reduces bugs introduced during maintenance.
The Core Tools: Prettier and ESLint
Prettier: An Opinionated Formatter
Prettier formats your code automatically with minimal configuration. It handles indentation, line length, quotes, semicolons, and trailing commas. Its key advantage is eliminating style debates — you accept Prettier's opinions and move on.
npm install --save-dev prettier
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"printWidth": 100,
"trailingComma": "es5"
}
npx prettier --write src/
ESLint: A Code Quality Linter
ESLint finds bugs, enforces best practices, and catches common errors. It is complementary to Prettier: ESLint handles code quality, Prettier handles formatting.
npm install --save-dev eslint @eslint/js
// eslint.config.js (flat config, ESLint 9+)
import js from "@eslint/js";
export default [
js.configs.recommended,
{
rules: {
"no-unused-vars": "warn",
"no-console": "warn",
"eqeqeq": "error",
}
}
];
Using Both Together
Use eslint-config-prettier to disable ESLint formatting rules that conflict with Prettier:
npm install --save-dev eslint-config-prettier
Indentation: Spaces vs. Tabs
The community has largely converged on 2 spaces for JavaScript/TypeScript. Arguments:
- 2 spaces: compact, readable at deep nesting levels, universally rendered
- 4 spaces: easier to distinguish levels, preferred in Python/C# communities
- Tabs: allows each developer to set their preferred visual width, but inconsistent in web environments
The most important thing is consistency within a project. Use .editorconfig to enforce it:
# .editorconfig
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
Naming Conventions
| Identifier | Convention | Example |
|---|---|---|
| Variables | camelCase | userName, isActive |
| Functions | camelCase | getUserById() |
| Classes | PascalCase | UserService |
| Constants | SCREAMING_SNAKE | MAX_RETRIES |
| Private fields | camelCase with _ prefix |
_cache |
| React components | PascalCase | UserProfile |
| Files (component) | PascalCase | UserProfile.tsx |
| Files (utility) | kebab-case | string-utils.ts |
Modern JavaScript Style Recommendations
Use const by Default, let When Needed
// Good
const maxRetries = 3;
let currentAttempt = 0;
// Avoid: var has function scope, not block scope
var old = "avoid this";
Prefer Arrow Functions for Callbacks
// Good
const doubled = numbers.map(n => n * 2);
// Verbose
const doubled = numbers.map(function(n) { return n * 2; });
Destructuring
// Good: clear, concise
const { name, email } = user;
const [first, ...rest] = items;
// Verbose
const name = user.name;
const email = user.email;
Optional Chaining and Nullish Coalescing
// Safe navigation
const city = user?.address?.city ?? "Unknown";
// Without optional chaining (verbose and error-prone)
const city = user && user.address && user.address.city ? user.address.city : "Unknown";
Template Literals
// Good
const message = `Hello, ${name}! You have ${count} messages.`;
// Concatenation (avoid)
const message = "Hello, " + name + "! You have " + count + " messages.";
Async Code Formatting
Async/Await vs Promises
// Good: async/await is more readable
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
console.error("Failed to fetch user:", error);
throw error;
}
}
// Promise chain (harder to read at multiple levels)
function fetchUser(id) {
return fetch(`/api/users/${id}`)
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
.catch(err => { console.error(err); throw err; });
}
Import Organisation
Group and order imports consistently:
// 1. Node built-ins
import { readFile } from "fs/promises";
import path from "path";
// 2. External packages
import express from "express";
import { z } from "zod";
// 3. Internal aliases / absolute paths
import { db } from "@/lib/database";
import { UserService } from "@/services/user";
// 4. Relative imports
import { formatDate } from "./utils";
import styles from "./Component.module.css";
Use eslint-plugin-import or a Prettier plugin to enforce import order automatically.
Git Hooks: Automate Formatting on Commit
npm install --save-dev husky lint-staged
npx husky init
// package.json
{
"lint-staged": {
"*.{js,ts,jsx,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,yaml}": ["prettier --write"]
}
}
With this setup, code is automatically formatted and linted before every commit, ensuring the repository always contains well-formatted code regardless of each developer's editor settings.
Conclusion
A consistent code style enforced by Prettier + ESLint + lint-staged eliminates an entire category of review comments and makes code maintenance easier. Invest 30 minutes setting up these tools at the start of a project — they save hours of back-and-forth over months of development. Use our JavaScript Formatter to quickly format code snippets without setting up a local toolchain.