NodeJS

Best Practices 


Introduction

Following best practices in Node.js development can help ensure your applications are efficient, secure, and maintainable. Here are some key best practices to follow:

  1. Code Structure and Organisation
  2. Error Handling
  3. Coding style
  4. Performance
  5. Logging
  6. Documentation
  7. Unit Testing
  8. Code Review

Code Structure and Organisation

  1. Modularize: Break your application into smaller, reusable modules. This makes the code more manageable and easier to maintain.
  2. Folder Structure: Organize your files in a logical folder structure. Common structures include separating routes, DTO, entities, controllers, models, services, etc.
  3. Environment Configuration: Use environment variables to manage configuration settings (e.g., using dotenv).
  4. .Updates: Regularly update dependencies, such as npm libraries, node versions, etc.
  5. Common Response Structure: Establish a common response structure to ensure consistent and easily identifiable responses across the platform. This structure will help maintain clarity and uniformity in communication.
  6. Sensitive Information:
    • Passwords: Never include passwords or password hashes.

    • Sensitive User Data: Avoid sending personal identification numbers (PINs), social security numbers (SSNs), or any other sensitive personal information.

    • Tokens and Keys: Exclude API keys, secret tokens, and other authentication credentials.

    • Inconsistent Naming: Mixing different naming conventions (e.g., camelCase, snake_case, PascalCase) within the same project.

  7. Reduce Redundancy: Implement a Generic Method: Minimize code repetition by creating and using a generic method that can be applied throughout the platform.
  8. Divide Large Function into Smaller Functions: Enhance code readability and maintainability by dividing large functions into smaller, more manageable ones.
  9. Use Proper Variable and Function Names: Improve code clarity and maintainability by using descriptive and appropriate variable and function names.
      • (Spell checker)

  10. SQL Injection Vulnerabilities: Failing to use parameterized queries or ORM libraries to prevent SQL injection.
  11. Generic Validation Messages in DTOs: Provide standardized, clear feedback for input errors, ensuring consistency and improving user experience. Store and manage these messages centrally to maintain uniformity across the application.

Error Handling

  1. Create proper try-catch to handle all errors
  2. Centralized error handling middleware: Use middleware to handle errors centrally in Express applications.
  3. Language-specific error messages: Return error messages according to the user's language preference.
  4. Proper error codes: Use standardized HTTP status codes and custom error codes for clarity.

Coding Style

  1. Use ESLint for code errors and fix code style
  2. For new projects, use Prettier to format code consistently. However, for older projects where Prettier is not yet implemented, be cautious when introducing it, as it may cause conflicts with existing formatting and code style.
  3. Start a Codeblock's Curly Braces on the Same Line
    1. function someFunction() {     // code block } 
  4. Prefer const over let. Ditch the var
    • var is function scoped, not block-scoped, and shouldn't be used in ES6 now that you have const and let at your disposal
  5. Use === operator     "" == "0"; // false     0 == ""; // true
  6.     0 == "0"; // true     false == "false"; // false     false == "0"; // true     false == undefined; // false     false == null; // false     null == undefined; // true     " \t\r\n " == 0; // true 
  7. Don't use queries in Loops.7. While uploading if any validation goes wrong then remove that asset from the server/storage.
  8. While uploading if any validation goes wrong then remove that asset from the server/storage.
  9. Don't declare variables inside the loops
  10. Reduce the usage of nested loops
    • If we want to use promise inside nested loops then we can use promise.all()
  11. Use clustering to utilize server threads.
  12. While using a cluster, handle CRONS accordingly, to avoid unwanted CRON executions.
  13. Remove all console before pushing it to production/staging
  14. Usage of Bulk Update

Performance

  1. Asynchronous Programming: Use asynchronous operations to avoid blocking the event loop (e.g., using async/await or Promises).
  2. Caching: Implement caching strategies to reduce load on the server (e.g., using Redis).
  3. Load Balancing: Use load balancers to distribute traffic across multiple instances.
  4. Profiling and Monitoring: Regularly profile and monitor your application to identify performance bottlenecks (e.g., using tools like New Relic or Prometheus).
  5. Minimal Packages: Use minimum packages if the same thing can be achievable using a small piece of code then do it that way. Before installing any package, check dependencies and install a package that has minimum dependencies.
  6. Database Caching: Implement caching strategies to reduce database load and improve performance. Store frequently accessed data in memory or use a caching service to minimize redundant database queries.
  7. Using Promise.all() for Concurrent Promises: When working with multiple prondividually. This approach can enhance performance by executing promimises, utilize Promise.all() to run them concurrently instead of awaiting each promise ises in parallel and aggregating their results..
  8. Garbage Collection: Ensure effective garbage collection by managing memoryusage efficiently. Avoid memory leaks by cleaning up unused objects and resources, and consider using tools or settings to monitor and optimize garbage collection processes in Node.js.
  9. Rate Limit: Properly implement and utilize rate limiting to manage and control the number of requests a user can make within a specified time period.

Logging

  1. Proper Error Logging: Ensure errors are logged in a way that is helpful for production environments. Include sufficient details to easily identify which logs pertain to specific bookings or other relevant entities. This approach facilitates efficient troubleshooting and monitoring by providing clear context and traceability for each error.
  2. Log Storage and Management: Store logs in a database or S3 to facilitate easy access and review of errors. Implement a system for periodically cleaning up old logs to manage storage efficiently. Ensure logs are well-organized and indexed for quick retrieval, and consider setting up automated processes for log rotation and archival to maintain performance and scalability.
  3. Email Logging (If Applicable): Implement email logging to capture detailed logs of email transactions, including both request and response data from the supplier. This approach enhances the ability to find and read logs by providing comprehensive records of email interactions. Ensure that logs are stored in a structured format, including timestamps, recipient addresses, subject lines, and any relevant response details, to facilitate efficient tracking and troubleshooting of email-related issues.
  4. Do not add logs in the server.

Documentation

  1. Properly manage swagger to document API requests and response
  2. Add comments before functions
  3. Add comments for complex logicImpact Analysis Documentation - Purpose: Create a comprehensive document detailing the impact analysis to be referred to before starting a specific module or task. This document aims to reduce the likelihood of affecting other methods or functions inadvertently.

Unit Testing

  1. Ensures Code Quality: Detects bugs early and validates that code functions as intended.
  2. Facilitates Refactoring: Allows safe code changes and improvements without introducing new bugs.
  3. Improves Code Design: Encourages modular, well-structured code and clarifies requirements.
  4. Enhances Debugging: Simplifies pinpointing defects and provides immediate feedback on code changes.
  5. Aids in Documentation: Acts as living documentation and helps future developers understand code behavior.Improves Reliability and Confidence: Ensures code reliability and boosts confidence in deployment.

Code Review

  1. Improves Code Quality: Detects bugs and ensures adherence to coding standards.
  2. Enhances Code Maintainability: Facilitates refactoring and knowledge sharing.
  3. Promotes Best Practices: Enforces standards and identifies security vulnerabilities.
  4. Increases Team Collaboration: Encourages peer review and provides constructive feedback.
  5. Reduces Technical Debt: Identifies and addresses issues early to prevent accumulation.
  6. Facilitates Knowledge Transfer: Aids onboarding and provides implicit documentation.
  7. Boosts Reliability and Stability: Ensures correct functionality and reduces production defects.
  8. Encourages Continuous Improvement: Enhances skills and refines development processes.

Suggestions

  1. Avoid Destructuring Everywhere: Pass the entire DTO object (e.g., User.create(userDto)) instead of destructuring to maintain adaptable and clean code.
  2. Direct Return of Async Functions: Return the response of an asynchronous function directly (e.g., return await getUserById(123)) to streamline code and enhance readability.
  3. Consistent Field Names: Keep the same field names in DTOs and database tables to avoid the need for reassigning fields, promoting cleaner and more efficient code.
  4. Use Entity Constants: Define constants for field lengths in a entity.constant.ts file and reuse them in validations to ensure consistency and reduce errors.
  5. Reverse Query Handling: Use reverse queries with transactions in TypeORM to handle large updates, ensuring database integrity if an error occurs.
  6. Fetch Required Fields Only: Retrieve only the necessary fields in queries to minimize data transfer, improve query performance, and reduce memory usage.
  7. Helper Function for Enum Values: Use a helper function to list all possible enum values in comments for better clarity and understanding of stored values.
  8. Descriptive Enum Values: Prefer using descriptive string values in enums for status, instead of numeric values, to make status values easier to manage and understand.
  9. Base Entity for Common Columns: Create a default base entity with common columns (e.g., id, createdAt, updatedAt) to be reused across multiple entities for consistency.
  10. Reduce Console Logging Time in VSCode: Use a plugin or extension in VSCode to reduce console logging time when working with NestJS applications. This can help streamline debugging and improve productivity (e.g Turbo Console Log).

0