Get the FREE Ultimate OpenClaw Setup Guide →

standards-javascript

Scanned
npx machina-cli add skill b33eep/claude-code-setup/standards-javascript --openclaw
Files (1)
SKILL.md
10.2 KB

JavaScript Coding Standards

Core Principles

  1. Simplicity: Simple, understandable code
  2. Readability: Readability over cleverness
  3. Maintainability: Code that's easy to maintain
  4. Testability: Code that's easy to test
  5. DRY: Don't Repeat Yourself - but don't overdo it

General Rules

  • Early Returns: Use early returns to avoid nesting
  • Descriptive Names: Meaningful names for variables and functions
  • Minimal Changes: Only change relevant code parts
  • No Over-Engineering: No unnecessary complexity
  • Minimal Comments: Code should be self-explanatory. No redundant comments!
  • Async/Await: Always use async/await for async operations

Naming Conventions

ElementConventionExample
Variables/FunctionscamelCasegetUserById, isActive
ClassesPascalCaseUserService, ApiClient
ConstantsUPPER_SNAKE_CASEMAX_RETRY_COUNT
PrivatePrefix with _ or #_internalMethod, #privateField
Fileskebab-case or camelCaseuser-service.js, userService.js
Event HandlersPrefix with handlehandleClick, handleSubmit

Project Structure

myproject/
├── src/
│   ├── index.js              # Entry point
│   ├── config.js             # Settings, env vars
│   ├── models/
│   │   └── user.js           # Domain models
│   ├── services/
│   │   └── user-service.js   # Business logic
│   ├── routes/
│   │   └── user-routes.js    # API routes
│   └── utils/
│       └── helpers.js        # Utility functions
├── tests/
│   ├── services/
│   │   └── user-service.test.js
│   └── setup.js
├── package.json
└── README.md

ES Modules (Preferred)

// math.js - Named exports
export function add(a, b) {
    return a + b;
}

export function multiply(a, b) {
    return a * b;
}

// app.js - Import
import { add, multiply } from './math.js';

// Default export
export default class UserService {
    // ...
}

// Import default
import UserService from './user-service.js';

package.json for ES Modules:

{
    "type": "module",
    "exports": {
        ".": "./src/index.js",
        "./utils": "./src/utils/index.js"
    }
}

Async/Await Patterns

// Always use async/await for async operations
async function fetchUserData(userId) {
    try {
        const response = await fetch(`https://api.example.com/users/${userId}`);

        if (!response.ok) {
            throw new Error(`API error: ${response.statusText}`);
        }

        return await response.json();
    } catch (error) {
        console.error('Fetch failed:', error);
        throw error;
    }
}

// Parallel execution with Promise.all
async function fetchMultipleUsers(userIds) {
    const users = await Promise.all(
        userIds.map(id => fetchUserData(id))
    );
    return users;
}

// Race with timeout
async function fetchWithTimeout(promise, timeout) {
    return Promise.race([
        promise,
        new Promise((_, reject) =>
            setTimeout(() => reject(new Error('Timeout')), timeout)
        ),
    ]);
}

// Get all results, including failures
async function fetchAllSettled(urls) {
    const results = await Promise.allSettled(
        urls.map(url => fetch(url).then(r => r.json()))
    );
    return results;
}

Best Practices

// Prefer const over let
const users = [];

// Use nullish coalescing and optional chaining
const name = user?.profile?.name ?? 'Anonymous';

// Prefer template literals
const message = `Hello, ${user.name}!`;

// Use destructuring
const { id, name, email } = user;
function processUser({ id, name }) { }

// Prefer array methods over loops
const activeUsers = users.filter(u => u.isActive);
const userNames = users.map(u => u.name);
const totalAge = users.reduce((sum, u) => sum + u.age, 0);

// Use Object.freeze for immutable constants
const CONFIG = Object.freeze({
    apiUrl: 'https://api.example.com',
    maxRetries: 3,
});

// Private class fields with #
class UserService {
    #apiClient;

    constructor(apiClient) {
        this.#apiClient = apiClient;
    }

    async getUser(id) {
        return this.#apiClient.get(`/users/${id}`);
    }
}

Express Framework

import express from 'express';

const app = express();
app.use(express.json());

// Routes
app.get('/users', (req, res) => {
    res.json([
        { id: 1, name: 'John' },
        { id: 2, name: 'Jane' },
    ]);
});

app.post('/users', (req, res) => {
    const { name } = req.body;
    res.status(201).json({ id: 3, name });
});

app.get('/users/:id', (req, res) => {
    const { id } = req.params;
    res.json({ id: parseInt(id), name: 'User' });
});

// Error handling middleware (must be last)
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).json({ error: 'Internal Server Error' });
});

app.listen(3000, () => {
    console.log('Server running on :3000');
});

Middleware Pattern:

// Logging middleware
const logger = (req, res, next) => {
    console.log(`${req.method} ${req.path}`);
    next();
};

// Authentication middleware
const authenticateToken = (req, res, next) => {
    const authHeader = req.headers['authorization'];
    const token = authHeader?.split(' ')[1];

    if (!token) {
        return res.status(401).json({ error: 'Missing token' });
    }

    // Verify token...
    req.user = decodedUser;
    next();
};

app.use(logger);
app.use('/api/protected', authenticateToken);

Fastify Framework

import Fastify from 'fastify';

const fastify = Fastify({ logger: true });

fastify.get('/users/:id', async (request, reply) => {
    const { id } = request.params;
    return { id: parseInt(id), name: 'User' };
});

fastify.post('/users', async (request, reply) => {
    const { name } = request.body;
    reply.code(201);
    return { id: 1, name };
});

fastify.listen({ port: 3000 }, (err, address) => {
    if (err) throw err;
    console.log(`Server listening at ${address}`);
});

Error Handling

// Custom error classes
class AppError extends Error {
    constructor(message, statusCode = 500) {
        super(message);
        this.name = 'AppError';
        this.statusCode = statusCode;
    }
}

class NotFoundError extends AppError {
    constructor(resource) {
        super(`${resource} not found`, 404);
        this.name = 'NotFoundError';
    }
}

// Result pattern for explicit error handling
function divide(a, b) {
    if (b === 0) {
        return { ok: false, error: 'Division by zero' };
    }
    return { ok: true, value: a / b };
}

const result = divide(10, 2);
if (result.ok) {
    console.log(result.value);
} else {
    console.error(result.error);
}

Testing with Vitest

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { add, multiply } from './math.js';

describe('Math utils', () => {
    it('should add numbers', () => {
        expect(add(2, 3)).toBe(5);
    });

    it('should multiply numbers', () => {
        expect(multiply(2, 3)).toBe(6);
    });
});

// Integration test example
describe('API Endpoints', () => {
    let server;

    beforeEach(() => {
        server = createServer();
    });

    afterEach(() => {
        server.close();
    });

    it('GET /users returns user list', async () => {
        const response = await fetch('http://localhost:3000/users');
        const data = await response.json();

        expect(response.ok).toBe(true);
        expect(Array.isArray(data)).toBe(true);
    });
});

Environment Configuration

// config.js
const config = {
    development: {
        port: 3000,
        database: 'mongodb://localhost:27017/mydb',
        logLevel: 'debug',
    },
    production: {
        port: process.env.PORT || 8080,
        database: process.env.DATABASE_URL,
        logLevel: 'info',
    },
};

const env = process.env.NODE_ENV || 'development';
export default config[env];

Comments - Less is More

// BAD - redundant comment
// Get the user from database
const user = repository.getUser(userId);

// GOOD - self-explanatory code, no comment needed
const user = repository.getUser(userId);

// GOOD - comment explains WHY (not obvious)
// Rate limit: API allows max 1000 requests/min
await rateLimiter.acquire();

Recommended Tooling

ToolPurpose
pnpm or bunPackage manager (faster than npm)
eslintLinting
prettierCode formatting
vitest or jestTesting framework
node --watchDevelopment with auto-reload
dotenvEnvironment variable management

package.json Template

{
    "name": "@org/myapp",
    "version": "1.0.0",
    "type": "module",
    "main": "src/index.js",
    "exports": {
        ".": "./src/index.js",
        "./utils": "./src/utils/index.js"
    },
    "engines": {
        "node": ">=22.0.0"
    },
    "scripts": {
        "dev": "node --watch src/index.js",
        "start": "node src/index.js",
        "test": "vitest",
        "lint": "eslint ."
    }
}

Production Best Practices

  1. ES Modules - Use "type": "module" in package.json
  2. Async/Await - Always use async/await for async operations
  3. Error Handling - Handle promise rejections properly
  4. Validation - Validate input in API endpoints
  5. Environment Variables - Use dotenv or similar for config
  6. Graceful Shutdown - Handle SIGTERM for clean exit
  7. Testing - Test with Vitest or Jest
  8. Linting - Use ESLint with recommended rules
  9. Security - Run npm audit regularly
  10. Process Management - Use PM2 for production

References

Source

git clone https://github.com/b33eep/claude-code-setup/blob/main/skills/standards-javascript/SKILL.mdView on GitHub

Overview

Standards-javascript enforces consistent, readable code across JavaScript projects. It codifies core principles like simplicity, readability, maintainability, and testability, while embracing ES2025 patterns, async handling, and recommended tooling.

How This Skill Works

The skill provides formal rules (core principles, general rules, naming conventions, and project structure) and concrete code examples for ES Modules and async/await usage. It is automatically loaded for JavaScript projects and guides tooling choices to align with modern JS practices.

When to Use It

  • Starting a new JavaScript project with a consistent semantic baseline
  • Refactoring legacy code to use async/await and reduce nesting
  • Enforcing naming, file structure, and private member conventions across a team
  • Configuring ES Modules, exports, and modern tooling in package.json
  • Setting up tests with supported frameworks (Vitest, Jest, Mocha) using standard patterns

Quick Start

  1. Step 1: Enable standards-javascript for your project (auto-loaded for JS projects)
  2. Step 2: Write code following ES Modules, async/await patterns, and naming conventions
  3. Step 3: Align project structure to the guidelines (src/, tests/, etc.) and run your tests

Best Practices

  • Prefer const over let and use descriptive identifiers
  • Apply early returns to reduce nesting and improve readability
  • Use nullish coalescing and optional chaining for safe access
  • Stick to camelCase for variables/functions, PascalCase for classes, and UPPER_SNAKE for constants
  • Always use async/await for asynchronous operations

Example Use Cases

  • A service module using ES Modules with named exports and a default class export
  • A routes file using camelCase names and kebab-case file names per conventions
  • A function fetching multiple users with Promise.all and proper error handling
  • A constants module using UPPER_SNAKE_CASE and private members prefixed with _ or #
  • A tests folder structure mirroring services with tests placed under tests/

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers