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

Mayank C
Tech Tonic

--

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 21 to 30.

The other parts in this series are:

21. Socket.io

Socket.IO is a facilitator of real-time communication in web applications. It overcomes the limitations of traditional HTTP requests and responses by establishing bi-directional, low-latency channels between clients and servers. This empowers developers to build dynamic and interactive experiences by instant data exchange and synchronized collaboration.

Statistics

  • Version: 4.7.4
  • Weekly Downloads: 4.4M
  • Repository Size: 1.32 MB
  • Total Files: 28
  • License: MIT

Code Samples

Server-side event broadcasting

const io = require('socket.io')();

io.on('connection', socket => {
socket.emit('news', 'A new user has joined!');
socket.on('chat message', message => {
io.emit('chat message', message); // Broadcast message to all connected clients
});
});

io.listen(3000);

Client-side connection and event handling

const socket = io('http://localhost:3000');

socket.on('news', message => {
console.log('Server message:', message);
});

socket.on('connect', () => {
console.log('Connected to the server!');
});

socket.emit('chat message', 'Hello from the client!');

Dynamic chat application with rooms

(function() {
const socket = io();
const chatForm = document.getElementById('chat-form');
const chatMessages = document.getElementById('chat-messages');

socket.on('chat message', (message, userName) => {
const newMessage = document.createElement('li');
newMessage.textContent = `${userName}: ${message}`;
chatMessages.appendChild(newMessage);
});

chatForm.addEventListener('submit', (event) => {
event.preventDefault();
const message = document.getElementById('chat-input').value;
socket.emit('chat message', message);
document.getElementById('chat-input').value = '';
});
})();

Pros

  • Real-time Communication: Enables instantaneous data exchange and bi-directional interaction between clients and servers.
  • Reduced Server Load: Offloads real-time processing from the server, improving scalability and performance.
  • Flexible Event System: Supports diverse event types and custom event naming for customized interactions.
  • Cross-platform Compatibility: Works across various browsers and platforms, including mobile devices.

Cons

  • Increased Complexity: Adds an additional layer of communication compared to traditional HTTP requests, requiring careful implementation.
  • Security Considerations: Real-time connections necessitate robust security measures to prevent unauthorized access and data breaches.
  • Debugging Challenges: Troubleshooting real-time interactions can be intricate due to their asynchronous nature.

22. TypeORM

TypeORM is a robust Object-Relational Mapper (ORM) for TypeScript and JavaScript, designed to bridge the gap between the objects in your code and the world of relational databases. It empowers you to interact with databases using familiar object-oriented paradigms, streamlining development and enhancing code maintainability.

Statistics

  • Version: 0.3.10
  • Weekly Downloads: 1.2M
  • Repository Size: 20 MB
  • Total Files: 2996
  • License: MIT

Code Samples

Defining a User entity

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;

@Column()
firstName: string;

@Column()
lastName: string;

@Column()
email: string;
}

Creating a repository and saving a user

import { getRepository } from 'typeorm';
import { User } from './user.entity';

const userRepository = getRepository(User);

const user = new User();
user.firstName = 'John';
user.lastName = 'Doe';
user.email = 'john.doe@example.com';

await userRepository.save(user);

Querying for users

const users = await userRepository.find();
const firstUser = await userRepository.findOne({ firstName: 'John' });

Pros

  • TypeScript Integration: Seamless integration with TypeScript for type safety and improved code quality.
  • Object-Oriented Approach: Treats database tables as classes and records as objects, enhancing code readability and maintainability.
  • Query Builder: Offers a flexible query builder for constructing complex queries in a type-safe manner.
  • Relationship Management: Supports various database relationships (one-to-one, one-to-many, many-to-many), simplifying data modeling.
  • Migration System: Manages database schema changes through migrations, ensuring consistency and version control.
  • Multiple Database Support: Works with multiple databases, including PostgreSQL, MySQL, MariaDB, SQLite, Microsoft SQL Server, and Oracle.

Cons

  • Learning Curve: Requires understanding of ORM concepts and potential overhead compared to raw SQL queries.
  • Performance Overhead: ORM abstraction can introduce some performance overhead in certain scenarios, necessitating careful optimization.
  • Vendor Lock-in: Reliance on a specific ORM can make switching databases more challenging.

23. Sequelize

Just like TypeORM, Sequelize also acts as a bridge between JavaScript objects and relational databases. This powerful Object-Relational Mapper (ORM) empowers developers to compose sophisticated database interactions using familiar object-oriented paradigms, transforming data access with ease.

Statistics

  • Version: 6.35.2
  • Weekly Downloads: 1.4M
  • Repository Size: 2.9 MB
  • Total Files: 300
  • License: MIT

Code Samples

Defining a User Model

const Sequelize = require('sequelize');

const sequelize = new Sequelize('database', 'username', 'password', {
dialect: 'mysql',
});

const User = sequelize.define('user', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
firstName: {
type: Sequelize.STRING,
},
lastName: {
type: Sequelize.STRING,
},
email: {
type: Sequelize.STRING,
unique: true,
},
});

module.exports = User;

Creating a User

const User = require('./user.model');

User.create({
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
})
.then(user => console.log('User created:', user))
.catch(error => console.error('Error creating user:', error));

Querying for Users

User.findAll({
where: {
email: 'john.doe@example.com',
},
})
.then(users => console.log('Found users:', users))
.catch(error => console.error('Error finding users:', error));

Pros

  • Expressive and Readable Code: Utilizes object-oriented concepts, making database interactions clearer and easier to maintain.
  • Supports Multiple Databases: Works seamlessly with popular databases like PostgreSQL, MySQL, MariaDB, SQLite, Microsoft SQL Server, and Oracle.
  • Powerful Query Builder: Enables constructing complex queries through a flexible and type-safe interface.
  • Relationship Management: Simplifies modeling and handling various database relationships, including one-to-one, one-to-many, and many-to-many.
  • Migration System: Facilitates database schema changes with migrations, ensuring version control and data integrity.
  • Extensive Community and Resources: Backed by a vibrant community and comprehensive documentation, offering support and guidance.

Cons

  • Learning Curve: Requires understanding ORM concepts and potentially more effort compared to raw SQL queries.
  • Performance Overhead: Abstraction layer can introduce performance overhead in some scenarios, requiring careful optimization.
  • Vendor Lock-in: Reliance on a specific ORM might make switching databases more challenging.

24. Joi

Joi empowers JavaScript developers with comprehensive object schema validation, ensuring data integrity and adherence to defined rules. It plays a crucial role in preventing errors, security vulnerabilities, and unexpected behavior by catching invalid data early in the development process.

Statistics

  • Version: 17.12.0
  • Weekly Downloads: 7.5M
  • Repository Size: 530 KB
  • Total Files: 36
  • License: MIT

Code Samples

Validating User Input

const Joi = require('joi');

const schema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
email: Joi.string().email({ minDomainSegments: 2 }).required(),
password: Joi.string().min(8).required(),
});

const { error, value } = Joi.validate(req.body, schema);

if (error) {
// Handle validation errors
} else {
// Process valid user data
}

Enforcing API Request Data Integrity

const schema = Joi.object({
id: Joi.number().integer().positive().required(),
name: Joi.string().required(),
price: Joi.number().required(),
quantity: Joi.number().integer().min(0).required(),
});

app.post('/products', async (req, res) => {
const { error, value } = Joi.validate(req.body, schema);

if (error) {
res.status(400).json({ error: error.details });
} else {
// Create product with validated data
}
});

Configuring Application Settings

const schema = Joi.object({
port: Joi.number().integer().default(3000),
mongoURI: Joi.string().required(),
logLevel: Joi.string().valid('debug', 'info', 'warn', 'error').default('info'),
});

const config = Joi.validate(process.env, schema).value;

Pros

  • Expressive schema language: Facilitates clear and concise schema definition.
  • Comprehensive validator set: Supports validation of diverse data types.
  • Customizable error messages: Enhances user experience and debugging.
  • Flexible configuration: Allows tailoring validation behavior to specific needs.

Cons

  • Potential performance overhead: Extensive validation can impact application performance, especially with large datasets.
  • Learning curve: Fluent schema language usage requires some investment in understanding its syntax and capabilities.

25. prettier

Prettier is as an opinionated code formatter, automating code style and formatting for a wide range of languages, including JavaScript, TypeScript, HTML, CSS, JSON, and more. It enforces consistent code style across projects and teams, enhancing readability and maintainability.

Statistics

  • Version: 3.2.4
  • Weekly Downloads: 27M
  • Repository Size: 8.3 MB
  • Total Files: 53
  • License: MIT

Code Samples

Formatting JavaScript Code

const unformattedCode = `
function add(x, y) {
return x + y;
}
`;

const formattedCode = prettier.format(unformattedCode, { parser: 'babel' });

console.log(formattedCode);

Formatting TypeScript Code

const unformattedTS = `
interface User {
name: string;
age: number;
}
`;

const formattedTS = prettier.format(unformattedTS, { parser: 'typescript' });

console.log(formattedTS);

Formatting Code Within a Project

npx prettier --write .

Pros

  • Opinionated: Eliminates style debates and ensures consistency.
  • Automatic: Formats code without manual intervention.
  • Configurable: Supports customization for specific preferences.
  • Wide language support: Extends to various programming and markup languages.
  • Editor integration: Works seamlessly with most popular code editors.

Cons

  • Limited customization: Opinionated nature restricts certain formatting choices.
  • Potential for unexpected changes: Automatic modifications might require careful review.

26. GraphQL

GraphQL offers a flexible and efficient approach to fetching and manipulating data in APIs. It empowers clients to specify precisely the data they need, reducing over- and under-fetching issues common in traditional REST APIs.

Statistics

  • Version: 16.8.1
  • Weekly Downloads: 8.8M
  • Repository Size: 1.3 MB
  • Total Files: 388
  • License: MIT

Code Samples

Defining a GraphQL Schema

const { GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLInt } = require('graphql');

const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: { type: GraphQLInt },
name: { type: GraphQLString },
email: { type: GraphQLString },
}),
});

const RootQuery = new GraphQLObjectType({
name: 'RootQuery',
fields: () => ({
user: {
type: UserType,
args: { id: { type: GraphQLInt } },
resolve: (parent, args) => getUserById(args.id),
},
}),
});

const schema = new GraphQLSchema({
query: RootQuery,
});

Executing a GraphQL Query

const query = `
query {
user(id: 1) {
id
name
email
}
}
`;

const result = await graphql(schema, query);

console.log(result);

Resolving GraphQL Fields

function getUserById(id) {
// Fetch user data from a database or other source
return { id, name: 'John Doe', email: 'johndoe@example.com' };
}

Pros

  • Client-driven: Clients specify exact data needs, reducing over- and under-fetching.
  • Strongly typed: Schema ensures data integrity and type safety.
  • Flexible: Adaptable to diverse data sources and application architectures.
  • Efficient: Potential for performance gains due to reduced data transfer.

Cons

  • Learning curve: Understanding GraphQL concepts and implementation requires effort.
  • Caching: Caching strategies can be more complex than traditional REST APIs.
  • Security: Requires careful attention to potential vulnerabilities.

27. ajv

Ajv serves as a fast and efficient JSON schema validator for JavaScript applications. It validates JSON data against defined schemas, ensuring its adherence to structural and semantic rules, fostering data integrity and application reliability.

Statistics

  • Version: 8.12.0
  • Weekly Downloads: 78M
  • Repository Size: 1 MB
  • Total Files: 466
  • License: MIT

Code Samples

Validating a Simple JSON Object

const Ajv = require('ajv');
const ajv = new Ajv(); // Optionally customize options here

const schema = {
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'integer', minimum: 18 },
},
required: ['name'],
};

const data = { name: 'John Doe', age: 30 };

const valid = ajv.validate(schema, data);

if (valid) {
console.log('Data is valid');
} else {
console.log(ajv.errorsText()); // Output validation errors
}

Validating an Array of Objects

const schema = {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
},
required: ['id', 'name'],
},
};

const data = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];

// Validation process as in the previous example

Using a Remote Schema

const schemaUrl = 'https://example.com/schemas/user.json';

ajv.addSchema(schemaUrl); // Fetch and compile the remote schema

// Validation process as in the previous examples

Pros

  • Performance: Ajv excels in speed and efficiency, outperforming many JSON schema validators.
  • Compliance: Adheres to multiple JSON schema drafts, ensuring compatibility.
  • Customizable: Offers options for error messages, formats, asynchronous loading, and more.
  • Integration: Works seamlessly with popular frameworks like Node.js, Express, and Koa.

Cons

  • Configuration: Customization options can introduce complexity in setup.
  • Error messages: Default error messages might require tailoring for clarity.

28. jest

Jest is a delightful JavaScript testing framework that prioritizes simplicity and ease of use. It streamlines the testing process, promoting code quality and reliability in JavaScript projects.

Statistics

  • Version: 29.7.0
  • Weekly Downloads: 17M
  • Repository Size: 5 KB
  • Total Files: 6
  • License: MIT

Code Samples

Basic Test Case

test('adds 1 + 2 to equal 3', () => {
expect(1 + 2).toBe(3);
});

Testing Asynchronous Code

test('fetches user data', async () => {
const response = await fetch('/api/users/1');
const user = await response.json();

expect(user.name).toBe('John Doe');
});

Testing React Components

import React from 'react';
import { render, screen } from '@testing-library/react';
import UserCard from './UserCard';

test('renders user name', () => {
render(<UserCard user={{ name: 'Alice' }} />);
const heading = screen.getByText(/Alice/i);
expect(heading).toBeInTheDocument();
});

Pros

  • Simplicity: Offers a straightforward and approachable testing experience.
  • Setup: Requires minimal configuration, often working out of the box.
  • Features: Includes snapshot testing, mocking, watch mode, code coverage, and more.

Cons

  • Customization: Certain advanced features might require more configuration than other frameworks.
  • Opinionated: Has strong opinions about testing practices, which might not align with all preferences.

29. helmet

Helmet serves as a middleware for Express-based Node.js applications, enhancing their security by setting various HTTP headers. These headers address common vulnerabilities, mitigate attacks, and protect sensitive information, creating a more secure web experience.

Statistics

  • Version: 7.1.0
  • Weekly Downloads: 2.6 M
  • Repository Size: 102 KB
  • Total Files: 9
  • License: MIT

Code Samples

Basic Usage

const express = require('express');
const helmet = require('helmet');

const app = express();

app.use(helmet()); // Apply all default security headers

Customizing Headers

app.use(helmet({
contentSecurityPolicy: false, // Disable specific headers if needed
frameguard: {
action: 'deny', // Configure options for individual headers
},
}));

Available Headers

  • contentSecurityPolicy: Mitigates cross-site scripting (XSS) and other injection attacks.
  • dnsPrefetchControl: Controls DNS prefetching behavior for performance optimization.
  • expectCt: Expects Certificate Transparency for enhanced security.
  • frameguard: Defends against clickjacking attacks.
  • hidePoweredBy: Removes the X-Powered-By header to conceal server identity.
  • hsts: Enforces HTTPS for secure connections.
  • ieNoOpen: Prevents Internet Explorer from opening files in a new window.
  • noSniff: Disables MIME sniffing to thwart content-sniffing attacks.
  • permittedCrossDomainPolicies: Specifies cross-domain policies for Adobe Flash Player.
  • referrerPolicy: Controls how the browser sends the Referer header for privacy protection.
  • xssFilter: Provides an additional layer of XSS protection.

Pros

  • Comprehensive: Covers a wide range of security headers.
  • Easy integration: Simple to incorporate into Express applications.
  • Customizable: Allows control over header settings.

Cons

  • Potential for conflicts: Might interact with other middleware or server configurations.
  • Requires understanding: Proper usage necessitates knowledge of security headers and their implications.

30. ramda

Ramda offers a practical functional programming library for JavaScript developers, empowering them to write concise and expressive code with a focus on immutability and side-effect free functions. It promotes a declarative style that enhances code readability and maintainability.

Statistics

  • Version: 0.29.1
  • Weekly Downloads: 9.6 M
  • Repository Size: 1.1 MB
  • Total Files: 710
  • License: MIT

Code Samples

Transforming Data

const R = require('ramda');

const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = R.map(R.multiply(2), numbers); // [2, 4, 6, 8, 10]

Composing Functions

const square = R.multiply(R.identity); // Creates a square function
const squaredAndEven = R.filter(R.modulo(2, 0), R.map(square, numbers)); // [4, 16]

Working with Objects

const user = { name: 'John Doe', age: 30, role: 'admin' };
const nameAndAge = R.pick(['name', 'age'], user); // { name: 'John Doe', age: 30 }

Pros

  • Immutability: Encourages pure functions that avoid side effects, promoting code predictability and easier testing.
  • Conciseness: Functional style often leads to more concise and readable code.
  • Composable: Functions can be easily combined to create complex logic.
  • Utility functions: Offers a wide range of helpful functions for common tasks.

Cons

  • Learning curve: Functional programming concepts might require time to grasp.
  • Performance overhead: Functional style can introduce performance costs in certain scenarios.

--

--