nodejs-best-practices

Node.js development principles and decision-making. Framework selection, async patterns, security, and architecture. Teaches thinking, not copying.

View Source
name:nodejs-best-practicesdescription:Node.js development principles and decision-making. Framework selection, async patterns, security, and architecture. Teaches thinking, not copying.allowed-tools:Read, Write, Edit, Glob, Grep

Node.js Best Practices

> Principles and decision-making for Node.js development in 2025.
> Learn to THINK, not memorize code patterns.


⚠️ How to Use This Skill

This skill teaches decision-making principles, not fixed code to copy.

  • ASK user for preferences when unclear

  • Choose framework/pattern based on CONTEXT

  • Don't default to same solution every time

  • 1. Framework Selection (2025)

    Decision Tree

    What are you building?

    ├── Edge/Serverless (Cloudflare, Vercel)
    │ └── Hono (zero-dependency, ultra-fast cold starts)

    ├── High Performance API
    │ └── Fastify (2-3x faster than Express)

    ├── Enterprise/Team familiarity
    │ └── NestJS (structured, DI, decorators)

    ├── Legacy/Stable/Maximum ecosystem
    │ └── Express (mature, most middleware)

    └── Full-stack with frontend
    └── Next.js API Routes or tRPC

    Comparison Principles

    FactorHonoFastifyExpress
    Best forEdge, serverlessPerformanceLegacy, learning
    Cold startFastestFastModerate
    EcosystemGrowingGoodLargest
    TypeScriptNativeExcellentGood
    Learning curveLowMediumLow

    Selection Questions to Ask:


  • What's the deployment target?

  • Is cold start time critical?

  • Does team have existing experience?

  • Is there legacy code to maintain?

  • 2. Runtime Considerations (2025)

    Native TypeScript

    Node.js 22+: --experimental-strip-types
    ├── Run .ts files directly
    ├── No build step needed for simple projects
    └── Consider for: scripts, simple APIs

    Module System Decision

    ESM (import/export)
    ├── Modern standard
    ├── Better tree-shaking
    ├── Async module loading
    └── Use for: new projects

    CommonJS (require)
    ├── Legacy compatibility
    ├── More npm packages support
    └── Use for: existing codebases, some edge cases

    Runtime Selection

    RuntimeBest For
    Node.jsGeneral purpose, largest ecosystem
    BunPerformance, built-in bundler
    DenoSecurity-first, built-in TypeScript


    3. Architecture Principles

    Layered Structure Concept

    Request Flow:

    ├── Controller/Route Layer
    │ ├── Handles HTTP specifics
    │ ├── Input validation at boundary
    │ └── Calls service layer

    ├── Service Layer
    │ ├── Business logic
    │ ├── Framework-agnostic
    │ └── Calls repository layer

    └── Repository Layer
    ├── Data access only
    ├── Database queries
    └── ORM interactions

    Why This Matters:


  • Testability: Mock layers independently

  • Flexibility: Swap database without touching business logic

  • Clarity: Each layer has single responsibility
  • When to Simplify:


  • Small scripts → Single file OK

  • Prototypes → Less structure acceptable

  • Always ask: "Will this grow?"

  • 4. Error Handling Principles

    Centralized Error Handling

    Pattern:
    ├── Create custom error classes
    ├── Throw from any layer
    ├── Catch at top level (middleware)
    └── Format consistent response

    Error Response Philosophy

    Client gets:
    ├── Appropriate HTTP status
    ├── Error code for programmatic handling
    ├── User-friendly message
    └── NO internal details (security!)

    Logs get:
    ├── Full stack trace
    ├── Request context
    ├── User ID (if applicable)
    └── Timestamp

    Status Code Selection

    SituationStatusWhen
    Bad input400Client sent invalid data
    No auth401Missing or invalid credentials
    No permission403Valid auth, but not allowed
    Not found404Resource doesn't exist
    Conflict409Duplicate or state conflict
    Validation422Schema valid but business rules fail
    Server error500Our fault, log everything


    5. Async Patterns Principles

    When to Use Each

    PatternUse When
    async/awaitSequential async operations
    Promise.allParallel independent operations
    Promise.allSettledParallel where some can fail
    Promise.raceTimeout or first response wins

    Event Loop Awareness

    I/O-bound (async helps):
    ├── Database queries
    ├── HTTP requests
    ├── File system
    └── Network operations

    CPU-bound (async doesn't help):
    ├── Crypto operations
    ├── Image processing
    ├── Complex calculations
    └── → Use worker threads or offload

    Avoiding Event Loop Blocking

  • Never use sync methods in production (fs.readFileSync, etc.)

  • Offload CPU-intensive work

  • Use streaming for large data

  • 6. Validation Principles

    Validate at Boundaries

    Where to validate:
    ├── API entry point (request body/params)
    ├── Before database operations
    ├── External data (API responses, file uploads)
    └── Environment variables (startup)

    Validation Library Selection

    LibraryBest For
    ZodTypeScript first, inference
    ValibotSmaller bundle (tree-shakeable)
    ArkTypePerformance critical
    YupExisting React Form usage

    Validation Philosophy

  • Fail fast: Validate early

  • Be specific: Clear error messages

  • Don't trust: Even "internal" data

  • 7. Security Principles

    Security Checklist (Not Code)

  • [ ] Input validation: All inputs validated

  • [ ] Parameterized queries: No string concatenation for SQL

  • [ ] Password hashing: bcrypt or argon2

  • [ ] JWT verification: Always verify signature and expiry

  • [ ] Rate limiting: Protect from abuse

  • [ ] Security headers: Helmet.js or equivalent

  • [ ] HTTPS: Everywhere in production

  • [ ] CORS: Properly configured

  • [ ] Secrets: Environment variables only

  • [ ] Dependencies: Regularly audited
  • Security Mindset

    Trust nothing:
    ├── Query params → validate
    ├── Request body → validate
    ├── Headers → verify
    ├── Cookies → validate
    ├── File uploads → scan
    └── External APIs → validate response


    8. Testing Principles

    Test Strategy Selection

    TypePurposeTools
    UnitBusiness logicnode:test, Vitest
    IntegrationAPI endpointsSupertest
    E2EFull flowsPlaywright

    What to Test (Priorities)

  • Critical paths: Auth, payments, core business

  • Edge cases: Empty inputs, boundaries

  • Error handling: What happens when things fail?

  • Not worth testing: Framework code, trivial getters
  • Built-in Test Runner (Node.js 22+)

    node --test src/*/.test.ts
    ├── No external dependency
    ├── Good coverage reporting
    └── Watch mode available


    10. Anti-Patterns to Avoid

    ❌ DON'T:


  • Use Express for new edge projects (use Hono)

  • Use sync methods in production code

  • Put business logic in controllers

  • Skip input validation

  • Hardcode secrets

  • Trust external data without validation

  • Block event loop with CPU work
  • ✅ DO:


  • Choose framework based on context

  • Ask user for preferences when unclear

  • Use layered architecture for growing projects

  • Validate all inputs

  • Use environment variables for secrets

  • Profile before optimizing

  • 11. Decision Checklist

    Before implementing:

  • [ ] Asked user about stack preference?

  • [ ] Chosen framework for THIS context? (not just default)

  • [ ] Considered deployment target?

  • [ ] Planned error handling strategy?

  • [ ] Identified validation points?

  • [ ] Considered security requirements?

  • > Remember: Node.js best practices are about decision-making, not memorizing patterns. Every project deserves fresh consideration based on its requirements.