Node.js: The commonly used NPM packages — Part 4

Mayank Choubey
Tech Tonic
8 min readJan 27, 2024

--

There is no denying that Node.js’s power comes from the 1.5M NPM packages. Without NPM, Node.js is still usable, but not much. In this article series, we will explore the commonly used NPM packages that every developer should be aware of. In this part, we’ll cover packages 31 to 40.

The other parts in this series are:

31. Prisma

Prisma serves as a next-generation ORM (Object-Relational Mapper) for Node.js and TypeScript applications, streamlining interactions with databases. It offers a type-safe API, auto-generated queries, and migrations, fostering a productive and error-free development experience.

Statistics

  • Weekly downloads: 1.7 M
  • Size: 14.3 M
  • Files: 87

Code Samples

Defining a Model

// prisma/schema.prisma
model User {
id Int @id @default(autoincrement())
name String?
email String @unique
createdAt DateTime @default(now())
}

Fetching Data

const users = await prisma.user.findMany();

Creating Data

const newUser = await prisma.user.create({
data: {
name: 'Alice',
email: 'alice@example.com',
},
});

Updating Data

const updatedUser = await prisma.user.update({
where: { id: 1 },
data: { name: 'Bob' },
});

Pros

  • Type safety: Ensures data integrity and prevents errors at compile time.
  • Auto-generated queries: Saves time and reduces the potential for errors.
  • Migrations: Manages database schema changes smoothly.
  • Performance: Optimized for efficient database interactions.

Cons

  • Learning curve: Understanding Prisma’s concepts and configuration might require some effort.
  • Abstraction: ORMs inherently abstract database interactions, potentially limiting control in certain scenarios.
  • Vendor lock-in: Primarily supports PostgreSQL, MySQL, SQLite, and SQL Server.

32. Day.js

Day.js offers a minimalist and performant JavaScript library for parsing, validating, manipulating, and displaying dates and times. It serves as a lightweight alternative to Moment.js, providing a similar API with a smaller footprint, enhancing efficiency and reducing bundle sizes.

Statistics

  • Weekly downloads: 15.5 M
  • Size: 664 KB
  • Files: 447

Code Samples

Creating a Date Object

import dayjs from 'dayjs';

const now = dayjs(); // Current date and time
const tomorrow = dayjs().add(1, 'day');
const yesterday = dayjs().subtract(1, 'day');

Formatting a Date

const formattedDate = now.format('YYYY-MM-DD HH:mm:ss'); // Output: 2024-01-26 18:40:00

Comparing Dates

const isFuture = tomorrow.isAfter(now); // true
const isPast = yesterday.isBefore(now); // true

Modifying Dates

const nextWeek = now.add(7, 'day');
const lastMonth = now.subtract(1, 'month');

Pros

  • Performance: Efficient and lightweight, often outperforming Moment.js.
  • Small size: Reduces bundle size, contributing to faster page loads.
  • Chainable: Methods can be chained for a fluent style.
  • Internationalization: Supports multiple languages and locales.
  • Plugin system: Extendable with plugins for additional functionality.

Cons

  • Limited features: May not offer the extensive feature set of Moment.js.
  • Community support: Smaller community compared to Moment.js.
  • Breaking changes: Potential for breaking changes as the library evolves.

33. Cypress

Cypress stands as a comprehensive end-to-end testing framework, specifically designed for web applications. It enables developers to write and execute tests that directly interact with the application in a browser, ensuring features function as intended from a user’s perspective.

Statistics

  • Weekly downloads: 4.4 M
  • Size: 7.2 M
  • Files: 862

Code Samples

Basic Test

describe('Login functionality', () => {
it('allows users to log in with valid credentials', () => {
cy.visit('/login');
cy.get('#username').type('johndoe');
cy.get('#password').type('password123');
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard'); // Assert successful login
});
});

Handling Asynchronous Actions

it('waits for data to load before asserting', () => {
cy.visit('/products');
cy.get('.product-list').should('be.empty'); // Initially empty
cy.get('#load-more-button').click();
cy.get('.product-list').should('have.length.greaterThan', 0); // Assert products loaded
});

Mocking and Stubbing

it('mocks API requests for offline testing', () => {
cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');
cy.visit('/users');
cy.wait('@getUsers'); // Wait for mocked response
cy.get('.user-list').should('have.length', 3); // Assert users displayed
});

Pros

  • Direct interaction: Tests run directly in the browser, closely mimicking user actions.
  • Readability: Emphasizes clear and concise test syntax, enhancing maintainability.
  • Headless testing: Supports execution without a visible browser, enabling integration into CI/CD pipelines.
  • Debugging: Offers powerful debugging tools for pinpointing test failures.
  • Video recording: Captures visual evidence of test execution for analysis and sharing.
  • Ecosystem: Integrates seamlessly with various testing tools and frameworks.

Cons

  • Performance: Can be slower than unit tests due to browser interactions.
  • Setup: Requires initial configuration and setup.
  • Learning curve: Understanding Cypress concepts and best practices might necessitate some effort.

34. Winston

Winston is a flexible and versatile logging library for Node.js applications. It empowers developers to log messages across various transport mechanisms, including consoles, files, cloud services, and third-party services, enabling comprehensive monitoring and debugging.

Statistics

  • Weekly downloads: 9.5 M
  • Size: 268 K
  • Files: 38

Code Samples

Basic Logging

const winston = require('winston');

const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'app.log' }),
],
});

logger.info('Application started');
logger.error('Error occurred:', error);

Custom Transports

const winstonRotatingFile = require('winston-daily-rotate-file');

logger.add(new winstonRotatingFile({ filename: 'error.log', level: 'error' }));

Multiple Levels

logger.debug('Debug message');
logger.info('Informational message');
logger.warn('Warning message');
logger.error('Error message');

Pros

  • Flexibility: Supports various transports, formats, and levels.
  • Customizable: Adaptable to specific logging needs.
  • Performance: Optimized for efficiency in production environments.
  • Ecosystem: Integrates with numerous third-party logging tools and services.

Cons

  • Configuration: Can require some setup for optimal usage.
  • Dependencies: Certain transports might introduce additional dependencies.

35. Express-rate-limit

Express-rate-limit serves as a middleware for Express.js applications, designed to protect against excessive requests and potential abuse. It enforces rate limits, ensuring a controlled flow of incoming traffic and safeguarding resources.

Statistics

  • Weekly downloads: 1.4 M
  • Size: 106 K
  • Files: 9

Code Samples

Basic Usage

const express = require('express');
const rateLimit = require('express-rate-limit');

const app = express();

const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
});

app.use(limiter);

Customizing Limits

// Limit based on IP address
app.get('/api/resource', limiter({ max: 10 }), (req, res) => {
// ...
});

// Limit based on route
app.get('/login', limiter({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // 5 requests per hour
}), (req, res) => {
// ...
});

Handling Rate Limit Exceeded

app.use((req, res, next) => {
if (req.headers['x-ratelimit-remaining'] === '0') {
return res.status(429).json({ message: 'Too many requests' });
}
next();
});

Pros

  • Simple integration: Easy to incorporate into Express applications.
  • Flexible configuration: Supports various rate-limiting strategies.
  • Storage options: Can use memory, Redis, or other stores for persistence.
  • Customizable responses: Allows tailored responses to rate-limited requests.

Cons

  • Potential for false positives: IP-based rate limiting might impact legitimate users behind shared IP addresses.
  • Additional complexity: Introduces configuration and storage considerations.
  • Performance overhead: Rate-limiting logic can slightly impact request processing time.

36. Semver

The semver package provides utilities for parsing, comparing, and manipulating semantic version strings. These strings adhere to a specific format (MAJOR.MINOR.PATCH) that communicates compatibility and indicates the significance of changes in software releases.

Statistics

  • Weekly downloads: 251 M
  • Size: 93 K
  • Files: 51

Code Samples

Parsing a Version String

const semver = require('semver');

const version = '1.2.3-beta.4+build.5';
const parsedVersion = semver.parse(version);

console.log(parsedVersion); // Output: { major: 1, minor: 2, patch: 3, prerelease: ['beta', '4'], build: ['build', '5'] }

Comparing Versions

semver.compare('1.2.3', '1.2.2'); // Output: 1 (greater than)
semver.lt('2.0.0', '1.10.0'); // Output: true (less than)
semver.satisfies('1.2.4', '>=1.2.0 <2.0.0'); // Output: true (satisfies range)

Creating Version Ranges

const range = semver.range('~1.2.0'); // Matches 1.2.x releases
const valid = semver.satisfies('1.2.3', range); // Output: true

Incrementing Versions

semver.inc('1.2.3', 'minor'); // Output: 1.3.0
semver.inc('1.2.3-beta.1', 'prerelease'); // Output: 1.2.3-beta.2

Pros

  • Standardization: Enforces consistent versioning practices.
  • Dependency management: Facilitates compatibility checks and conflict resolution.
  • Version ranges: Enables flexible dependency specifications.
  • Change communication: Clearly conveys the significance of updates.
  • Wide adoption: Supported by numerous tools and registries.

Cons

  • Limited flexibility: Strict adherence might not always align with release strategies.
  • Potential for confusion: Misunderstanding of versioning rules can lead to issues.

37. Superagent

Superagent offers a lightweight, progressive client-side HTTP request library with a flexible API, supporting both browser and Node.js environments. It streamlines interactions with web servers and APIs, making it a popular choice for various web development tasks.

Statistics

  • Weekly downloads: 7.3 M
  • Size: 539 K
  • Files: 20

Code Samples

Basic GET Request

const request = require('superagent');

request
.get('https://api.example.com/users')
.then(response => {
console.log(response.body);
})
.catch(error => {
console.error(error);
});

POST Request with Data

request
.post('https://api.example.com/users')
.send({ name: 'John Doe', email: 'johndoe@example.com' })
.then(response => {
console.log(response.body);
});

Handling Headers and Authentication

request
.get('https://api.example.com/protected-resource')
.set('Authorization', 'Bearer YOUR_API_TOKEN')
.then(response => {
// ...
});

Chaining Requests

request
.get('https://api.example.com/posts')
.then(response => {
const postId = response.body[0].id;
return request.get(`https://api.example.com/posts/${postId}`);
})
.then(response => {
console.log(response.body);
});

Pros

  • Browser-friendly: Functions seamlessly in both browsers and Node.js.
  • Chainable API: Facilitates building complex request flows with ease.
  • Promise-based: Simplifies asynchronous handling and error management.
  • Customizable: Offers various options for tailoring requests and responses.

Cons

  • Not as feature-rich: Might lack certain advanced features found in other libraries.
  • Learning curve: Chaining syntax might require some practice.

38. Axios-retry

Axios-retry extends the popular Axios HTTP client library with automatic retry functionality. It enables applications to gracefully handle transient errors and network issues by automatically reattempting failed requests, enhancing resilience and reliability.

Statistics

  • Weekly downloads: 2.1 M
  • Size: 25 K
  • Files: 9

Code Samples

Basic Usage with Axios

const axios = require('axios');
const axiosRetry = require('axios-retry');

const instance = axios.create();
axiosRetry(instance, {
retries: 3, // Retry up to 3 times
});

instance.get('https://api.example.com/data')
.then(response => {
// Handle successful response
})
.catch(error => {
// Handle errors after retries have been exhausted
});

Customizing Retry Behavior

axiosRetry(instance, {
retries: 5,
retryDelay: (retryCount) => Math.min(1000 * Math.pow(2, retryCount), 10000), // Exponential backoff
retryCondition: (error) => error.response.status >= 500, // Only retry on 5xx errors
});

Pros

  • Improved resilience: Enhances application robustness against temporary network issues.
  • Ease of use: Simple integration with Axios instances.
  • Customizable: Configurable retry strategies and conditions.

Cons

  • Dependency: Relies on the Axios library.
  • Potential for abuse: Over-reliance on retries might mask underlying issues.
  • Additional configuration: Retry logic requires careful setup.

39. JS-yaml

js-yaml serves as a YAML parser and stringifier for JavaScript, allowing seamless integration of YAML data into your Node.js applications. YAML is a human-readable data serialization format commonly used for configuration files, data exchange, and more.

Statistics

  • Weekly downloads: 67 M
  • Size: 405 K
  • Files: 33

Code Samples

Parsing YAML

const yaml = require('js-yaml');

const data = yaml.safeLoad('name: John Doe\nage: 30\noccupation: Developer');
console.log(data); // Output: { name: 'John Doe', age: 30, occupation: 'Developer' }

Stringifying JavaScript objects

const obj = { name: 'Alice', hobbies: ['coding', 'reading'] };
const yamlString = yaml.safeDump(obj);
console.log(yamlString); // Output: 'name: Alice\nhobbies:\n - coding\n - reading\n'

Pros

  • Ease of use: Straightforward parsing and stringifying methods.
  • Safe mode: Protects against arbitrary code execution during parsing.
  • Customizable: Offers options for handling schema validation and circular references.

Cons

  • Performance: Can be slower than native JSON parsing for large datasets.
  • Schema validation: Lacks built-in schema validation for enforcing data structure.

40. Mime-types

The mime-types package provides comprehensive utilities for working with MIME types (Multipurpose Internet Mail Extensions), which are standardized identifiers used to describe the content type of files and data over the internet. It’s essential for correctly handling various file formats in Node.js applications.

Statistics

  • Weekly downloads: 41 M
  • Size: 18 K
  • Files: 5

Code Samples

Retrieving MIME Type by Extension

const mime = require('mime-types');

const mimeType = mime.lookup('filename.jpg'); // Output: 'image/jpeg'

Determining MIME Type by Content

const mimeType = mime.lookup(Buffer.from('Some text content')); // Output: 'text/plain'

Getting Extensions for a MIME Type

const extensions = mime.extensions('text/html'); // Output: ['html', 'htm']

Pros

  • Extensive database: Covers a wide range of MIME types.
  • Simple API: Easy to use with intuitive methods.
  • Reliable: Accurately identifies MIME types for common file formats.

Cons

  • Limited customizability: Cannot easily add unknown MIME types.
  • Potential for errors: Might misidentify MIME types for less common or custom file formats.

--

--