skills/tejovanthn/rasikalife/javascript-excellence

javascript-excellence

SKILL.md

JavaScript Excellence

This skill embodies DHH's coding philosophy adapted for JavaScript and TypeScript - emphasizing clarity, simplicity, and expressiveness.

Core Principles

  1. Clarity over Cleverness: Code should be obvious, not clever
  2. Simplicity over Complexity: The simplest solution is usually the best
  3. Expressiveness: Code should read like prose
  4. DRY (Don't Repeat Yourself): Eliminate duplication ruthlessly
  5. Convention over Configuration: Sensible defaults, customize when needed
  6. No Premature Abstraction: Wait until patterns emerge

Language Best Practices

Use Modern JavaScript Features

Do use modern syntax:

// Destructuring
const { name, email } = user;

// Spread operators
const newUser = { ...user, verified: true };

// Optional chaining
const street = user?.address?.street;

// Nullish coalescing
const name = user.name ?? "Anonymous";

// Template literals
const greeting = `Hello, ${name}!`;

Don't write old-style code:

// Don't
var name = user.name ? user.name : "Anonymous";
var newUser = Object.assign({}, user, { verified: true });

Prefer Const and Let

Do use const by default:

const users = await getUsers();
const total = users.length;

Don't use var:

var users = await getUsers();  // No!

Use Meaningful Names

Do use descriptive names:

async function sendWelcomeEmail(user: User) {
  const emailContent = buildWelcomeEmail(user);
  await emailService.send(emailContent);
}

Don't use cryptic abbreviations:

async function sndWlcmEml(u: User) {  // What?
  const ec = bldWlcmEml(u);
  await es.snd(ec);
}

TypeScript Best Practices

Use Type Inference

Do let TypeScript infer:

const users = await db.user.findMany();  // Type is inferred
const count = users.length;               // Type is inferred

function double(n: number) {
  return n * 2;  // Return type inferred as number
}

Don't over-specify:

const users: User[] = await db.user.findMany();  // Redundant
const count: number = users.length;               // Redundant

function double(n: number): number {  // Return type unnecessary
  return n * 2;
}

Use Discriminated Unions for State

Do model states explicitly:

type LoadingState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; error: Error };

function render(state: LoadingState<User>) {
  switch (state.status) {
    case "idle":
      return <div>Not started</div>;
    case "loading":
      return <div>Loading...</div>;
    case "success":
      return <div>{state.data.name}</div>;  // data is available!
    case "error":
      return <div>Error: {state.error.message}</div>;
  }
}

Don't use boolean flags:

type LoadingState<T> = {
  isLoading: boolean;
  isError: boolean;
  data?: T;
  error?: Error;
};
// Can have invalid states like isLoading=true and isError=true

Keep Types Simple

Do use simple types:

type User = {
  id: string;
  name: string;
  email: string;
};

type CreateUserInput = Pick<User, "name" | "email">;

Don't over-engineer types:

type DeepPartial<T> = T extends object
  ? { [P in keyof T]?: DeepPartial<T[P]> }
  : T;

type RecursiveRequired<T> = {
  [P in keyof T]-?: RecursiveRequired<T[P]>;
};
// Unless you really need this complexity!

Function Patterns

Keep Functions Small and Focused

Do write focused functions:

async function createUser(data: CreateUserInput) {
  const hashedPassword = await hashPassword(data.password);
  const user = await db.user.create({
    data: { ...data, password: hashedPassword }
  });
  await sendWelcomeEmail(user);
  return user;
}

async function hashPassword(password: string) {
  return bcrypt.hash(password, 10);
}

async function sendWelcomeEmail(user: User) {
  // Email logic here
}

Don't write giant functions:

async function createUser(data: CreateUserInput) {
  // 200 lines of mixed concerns
  // password hashing
  // validation
  // database access
  // email sending
  // logging
  // etc.
}

Use Early Returns

Do use guard clauses:

function processPayment(amount: number, user: User) {
  if (amount <= 0) {
    throw new Error("Invalid amount");
  }
  
  if (!user.paymentMethod) {
    throw new Error("No payment method");
  }
  
  if (user.balance < amount) {
    throw new Error("Insufficient balance");
  }
  
  // Happy path at the end
  return chargePayment(user, amount);
}

Don't nest deeply:

function processPayment(amount: number, user: User) {
  if (amount > 0) {
    if (user.paymentMethod) {
      if (user.balance >= amount) {
        return chargePayment(user, amount);
      } else {
        throw new Error("Insufficient balance");
      }
    } else {
      throw new Error("No payment method");
    }
  } else {
    throw new Error("Invalid amount");
  }
}

Use Async/Await Consistently

Do use async/await:

async function loadUserData(userId: string) {
  const user = await db.user.findUnique({ where: { id: userId } });
  const posts = await db.post.findMany({ where: { userId } });
  return { user, posts };
}

Don't mix promises and async/await:

async function loadUserData(userId: string) {
  const user = await db.user.findUnique({ where: { id: userId } });
  return db.post.findMany({ where: { userId } }).then(posts => {
    return { user, posts };
  });
}

Object and Array Patterns

Use Destructuring

Do destructure:

function createPost({ title, content, authorId }: CreatePostInput) {
  return db.post.create({
    data: { title, content, authorId }
  });
}

const { name, email } = user;
const [first, second, ...rest] = items;

Use Array Methods

Do use functional array methods:

const activeUsers = users.filter(u => u.active);
const userNames = users.map(u => u.name);
const totalScore = scores.reduce((sum, score) => sum + score, 0);
const hasAdmin = users.some(u => u.role === "admin");

Don't use for loops unnecessarily:

const activeUsers = [];
for (let i = 0; i < users.length; i++) {
  if (users[i].active) {
    activeUsers.push(users[i]);
  }
}

Use Object Methods

Do use Object methods:

const keys = Object.keys(user);
const values = Object.values(user);
const entries = Object.entries(user);

const merged = Object.assign({}, defaults, overrides);
// Or better:
const merged = { ...defaults, ...overrides };

Error Handling

Use Try-Catch for Async

Do handle errors:

async function getUser(id: string) {
  try {
    return await db.user.findUnique({ where: { id } });
  } catch (error) {
    logger.error("Failed to fetch user", { error, id });
    throw new Error("User not found");
  }
}

Create Custom Errors

Do use custom error classes:

class NotFoundError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "NotFoundError";
  }
}

class ValidationError extends Error {
  constructor(message: string, public fields: string[]) {
    super(message);
    this.name = "ValidationError";
  }
}

// Usage
if (!user) {
  throw new NotFoundError("User not found");
}

Code Organization

Group Related Code

Do organize by feature:

src/
  features/
    auth/
      login.ts
      signup.ts
      session.ts
    posts/
      create.ts
      list.ts
      edit.ts

Don't organize by type:

src/
  controllers/
    authController.ts
    postController.ts
  services/
    authService.ts
    postService.ts
  models/
    user.ts
    post.ts

Extract Reusable Logic

Do extract when patterns emerge:

// After seeing this pattern 3+ times
function requireAuth<T>(
  handler: (userId: string, input: T) => Promise<Response>
) {
  return async (input: T) => {
    const userId = await getUserId();
    if (!userId) {
      throw redirect("/login");
    }
    return handler(userId, input);
  };
}

// Use it
export const loader = requireAuth(async (userId, { params }) => {
  // userId is guaranteed to exist
});

Don't abstract too early:

// Don't create "BaseController" after one use
class BaseController {
  // Generic abstraction nobody asked for
}

Comments and Documentation

Write Self-Documenting Code

Do write clear code:

function calculateTotalPrice(items: CartItem[]) {
  const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  const tax = subtotal * TAX_RATE;
  return subtotal + tax;
}

Don't comment obvious code:

// Calculate total price
function calc(items: CartItem[]) {
  // Sum all items
  const s = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  // Calculate tax
  const t = s * TAX_RATE;
  // Return total
  return s + t;
}

Comment the "Why", Not the "What"

Do explain reasoning:

// We disable scroll lock on mobile because iOS Safari has issues
// with position:fixed when the keyboard is open. See issue #123
if (isMobile) {
  disableScrollLock();
}

Don't state the obvious:

// Check if mobile
if (isMobile) {
  // Disable scroll lock
  disableScrollLock();
}

Testing Patterns

Write Readable Tests

Do write clear tests:

test("creates user with hashed password", async () => {
  const input = { email: "test@example.com", password: "secret123" };
  
  const user = await createUser(input);
  
  expect(user.email).toBe(input.email);
  expect(user.password).not.toBe(input.password);
  expect(await verifyPassword(input.password, user.password)).toBe(true);
});

Test Behavior, Not Implementation

Do test outcomes:

test("returns 404 when post not found", async () => {
  const response = await app.request("/posts/invalid-id");
  expect(response.status).toBe(404);
});

Don't test internals:

test("calls database with correct query", async () => {
  const spy = vi.spyOn(db, "query");
  await getPost("123");
  expect(spy).toHaveBeenCalledWith({ id: "123" });
});

Anti-Patterns to Avoid

1. Callback Hell

Don't nest callbacks:

getUser(userId, (error, user) => {
  if (error) return handleError(error);
  getPosts(user.id, (error, posts) => {
    if (error) return handleError(error);
    // ...more nesting
  });
});

Do use async/await:

const user = await getUser(userId);
const posts = await getPosts(user.id);

2. Mutation Everywhere

Don't mutate unnecessarily:

function addItem(cart: Cart, item: Item) {
  cart.items.push(item);  // Mutates input
  return cart;
}

Do prefer immutability:

function addItem(cart: Cart, item: Item) {
  return {
    ...cart,
    items: [...cart.items, item]
  };
}

3. God Objects

Don't create giant classes:

class UserManager {
  createUser() {}
  updateUser() {}
  deleteUser() {}
  authenticateUser() {}
  sendEmail() {}
  generateReport() {}
  // 50 more methods...
}

Do use focused modules:

// user.ts
export function createUser() {}
export function updateUser() {}

// auth.ts
export function authenticate() {}

// email.ts
export function sendEmail() {}

Remember

DHH's philosophy in JavaScript:

  • Write code that reads naturally
  • Prefer simplicity over cleverness
  • Eliminate duplication
  • Keep functions small and focused
  • Use the language's features effectively
  • Don't abstract too early
  • Make invalid states unrepresentable
  • Test behavior, not implementation

Your code should be a joy to read and maintain.

Weekly Installs
8
First Seen
9 days ago
Installed on
opencode8
gemini-cli8
claude-code8
github-copilot8
codex8
kimi-cli8