<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Antonello Zanini</title>
    <description>The latest articles on DEV Community by Antonello Zanini (@antozanini).</description>
    <link>https://dev.to/antozanini</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1009144%2F5b2a242b-4a32-42ff-8a10-31ff4b777178.jpeg</url>
      <title>DEV Community: Antonello Zanini</title>
      <link>https://dev.to/antozanini</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/antozanini"/>
    <language>en</language>
    <item>
      <title>CapMonster Cloud Review: The All-in-One CAPTCHA Solving Service</title>
      <dc:creator>Antonello Zanini</dc:creator>
      <pubDate>Wed, 08 Oct 2025 09:58:56 +0000</pubDate>
      <link>https://dev.to/antozanini/capmonster-cloud-review-the-all-in-one-captcha-solving-service-58mb</link>
      <guid>https://dev.to/antozanini/capmonster-cloud-review-the-all-in-one-captcha-solving-service-58mb</guid>
      <description>&lt;p&gt;Raise your hand if you think CAPTCHAs are annoying. Well, you’re not alone… For human users, CAPTCHA challenges are frustrating, but for automation workflows (e.g., web scraping bots or AI agents), they represent a serious roadblock!&lt;/p&gt;

&lt;p&gt;That’s why, over the past few years, several CAPTCHA-solving services have popped up. They all promise to solve CAPTCHAs programmatically, but can they really deliver? Today, I’m reviewing CapMonster Cloud, one of the major players in the CAPTCHA-solving industry, to see if it lives up to the hype.&lt;/p&gt;

&lt;p&gt;Follow me on this journey as I explain what the company is, the services they provide, how it all works with practical examples, their pricing, and—of course—a final verdict!&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is CapMonster Cloud?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://capmonster.cloud/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;CapMonster Cloud&lt;/a&gt; is an online service that leverages artificial intelligence to quickly solve various types of CAPTCHAs in the cloud. Its solutions are designed for anyone who needs to bypass CAPTCHA challenges during web scraping, automation, or testing workflows. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F80cpf79i4dbubxccvr6m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F80cpf79i4dbubxccvr6m.png" alt="CapMonster Cloud’s logo" width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This service is developed by &lt;a href="https://zennolab.com/" rel="noopener noreferrer"&gt;ZennoLab&lt;/a&gt;, a company that provides a comprehensive suite of automation solutions.&lt;/p&gt;

&lt;p&gt;CapMonster Cloud is trusted by over 1,000 businesses worldwide and &lt;strong&gt;has received excellent user feedback&lt;/strong&gt;, including a &lt;a href="https://www.trustpilot.com/review/capmonster.cloud" rel="noopener noreferrer"&gt;4.8/5 rating on Trustpilot&lt;/a&gt;, &lt;a href="https://slashdot.org/software/p/CapMonster-Cloud/" rel="noopener noreferrer"&gt;5/5 on Slashdot&lt;/a&gt;, and &lt;a href="https://sourceforge.net/software/product/CapMonster-Cloud/" rel="noopener noreferrer"&gt;4.8/5 on SourceForge&lt;/a&gt; based on more than 120 verified reviews.&lt;/p&gt;

&lt;h3&gt;
  
  
  Main Features
&lt;/h3&gt;

&lt;p&gt;These are the main features, capabilities, and aspects that characterize CapMonster Cloud:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI-powered cloud-based CAPTCHA solving for automation tasks.&lt;/li&gt;
&lt;li&gt;Reliable and fast API designed for high scalability.&lt;/li&gt;
&lt;li&gt;Browser extensions for Chrome and Firefox for automatic CAPTCHA solving.&lt;/li&gt;
&lt;li&gt;Ready-to-use SDKs in Python, JavaScript, C#, Go, and PHP.&lt;/li&gt;
&lt;li&gt;Support for &lt;a href="https://docs.capmonster.cloud/docs/external?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;integration with external services&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Support for a wide range of CAPTCHA types, including reCAPTCHA v2/v3, Cloudflare Turnstile, DataDome, Tencent CAPTCHA, Amazon WAF, GeeTest, Imperva, Text CAPTCHAs, and others.&lt;/li&gt;
&lt;li&gt;Ethical and transparent CAPTCHA automation principles.&lt;/li&gt;
&lt;li&gt;Affiliate program available.&lt;/li&gt;
&lt;li&gt;Fast and responsive customer support, available even on Telegram.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Product Lineup: CAPTCHA-Solving Solutions
&lt;/h2&gt;

&lt;p&gt;Let me introduce the products offered by CapMonster Cloud API. Here, I’ll analyze how they work, the experience they provide, and their pros and cons.&lt;/p&gt;

&lt;p&gt;For a quick overview, take a look at the summary table below:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;CapMonster Cloud API&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;CapMonster Cloud Browser Extensions&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Cloud-based CAPTCHA-solving service for automation and development&lt;/td&gt;
&lt;td&gt;Browser-based tool to automatically solve CAPTCHAs while browsing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Platform&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Any tool or programming language that can make HTTP requests&lt;/td&gt;
&lt;td&gt;Chrome and Firefox&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Call API endpoints authenticated with your CapMonster Cloud API key to send CAPTCHA data and receive the solution&lt;/td&gt;
&lt;td&gt;Install the extension, enter your API key, and let the extension solve the CAPTCHA for you&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What I can say before diving into the details is that an &lt;strong&gt;API service and browser extensions are the two products you’d expect from a top-tier CAPTCHA solver&lt;/strong&gt;. On top of that, as you’re about to discover, each comes with some unique perks that make them stand out. &lt;/p&gt;

&lt;p&gt;Time to learn more!&lt;/p&gt;

&lt;h3&gt;
  
  
  CapMonster Cloud API
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://capmonster.cloud/en/captcha-api?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;CapMonster Cloud API&lt;/a&gt; is a cloud CAPTCHA-solving service that automates CAPTCHA recognition and bypass. It exposes several HTTP endpoints so that you can send CAPTCHA data to the cloud and receive a solution in response. That’s ideal for developers seeking a programming language–agnostic CAPTCHA-solving solution.&lt;/p&gt;

&lt;p&gt;In particular, the main endpoints exposed by CapMonster Cloud are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.capmonster.cloud/docs/api/methods/create-task?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;&lt;strong&gt;POST &lt;code&gt;/createTask&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: Create a CAPTCHA solving task. &lt;a href="https://docs.capmonster.cloud/docs/captchas/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;Discover all supported CAPTCHA types&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.capmonster.cloud/docs/api/methods/get-task-result?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;&lt;strong&gt;POST &lt;code&gt;/getTaskResult&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: To poll the task and retrieve the solution to the CAPTCHA.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.capmonster.cloud/docs/api/methods/get-balance?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;&lt;strong&gt;POST &lt;code&gt;/getBalance&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: To check your account balance.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.capmonster.cloud/docs/api/methods/get-user-agent?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;&lt;strong&gt;GET &lt;code&gt;/getUserAgent&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;: Returns the latest &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/User-Agent" rel="noopener noreferrer"&gt;&lt;code&gt;User-Agent&lt;/code&gt;&lt;/a&gt; string for Chrome on Windows. Setting a valid &lt;code&gt;User-Agent&lt;/code&gt; helps the cloud solver better emulate a real browser and reduces blocks for certain CAPTCHA types.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: &lt;code&gt;/getBalance&lt;/code&gt; and &lt;code&gt;/getUserAgent&lt;/code&gt; are there only for account management and improving accuracy, respectively. While not strictly required, they’re a nice-to-have addition!&lt;/p&gt;

&lt;p&gt;Given the available endpoints, this is how the CAPTCHA-solving process works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a task&lt;/strong&gt;: Call &lt;code&gt;/createTask&lt;/code&gt; with your Cloud Monster API key, the task type for your CAPTCHA type, and CAPTCHA details (e.g., key, URL, etc.). This API returns a &lt;code&gt;taskId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poll for a result&lt;/strong&gt;: Poll &lt;code&gt;/getTaskResult&lt;/code&gt; with your CapMonster Cloud API key. When the CAPTCHA-solving task finishes, the API will return a &lt;code&gt;"ready"&lt;/code&gt; status and an object containing the solution for your task type (e.g., the text answer, a &lt;code&gt;gRecaptchaResponse&lt;/code&gt; token, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the solution&lt;/strong&gt;: After receiving the solution, use it to solve the CAPTCHA or submit the token to validate the target site’s form, API call, page loading, etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As you can see, the process is pretty simple and similar to other CAPTCHA-solving services. So, nothing new here!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚙️ Special features&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extra endpoints for account management (&lt;code&gt;/getBalance&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Public API (&lt;code&gt;/getUserAgent&lt;/code&gt;) to fetch the &lt;code&gt;User-Agent&lt;/code&gt; header set by the latest version of Chrome on Windows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👍 &lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supports ~20 different CAPTCHA types, each clearly documented and with snippets in C#, Python, JS, Go, and PHP.&lt;/li&gt;
&lt;li&gt;Intuitive API-based workflow using only two main endpoints: &lt;code&gt;/createTask&lt;/code&gt; and &lt;code&gt;/getTaskResult&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Cloud-based, requiring no software installation, and usable from any programming language that can make HTTP requests.&lt;/li&gt;
&lt;li&gt;Support for custom proxy integration.&lt;/li&gt;
&lt;li&gt;Clear error descriptions, with technical support available via a support form, the Telegram channel, or live chat on the website.&lt;/li&gt;
&lt;li&gt;High-level SDKs in Python, JavaScript, C#, Go, and PHP, so you don’t have to call the APIs directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👎 &lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.capmonster.cloud/docs/api/block-reason?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;Temporary bans&lt;/a&gt; (usually ~10 minutes) occur for repeated errors, such as requests without a key or with an invalid key, requests on a zero account balance, exceeding the task check limit (~120 per task), etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Browser Extensions
&lt;/h3&gt;

&lt;p&gt;CapMonster Cloud also comes with dedicated &lt;a href="https://capmonster.cloud/en/browser-extension-captcha" rel="noopener noreferrer"&gt;browser extensions&lt;/a&gt;, which enable you to automatically solve CAPTCHAs encountered while browsing. These are &lt;strong&gt;available for Chrome and Firefox&lt;/strong&gt;, and they connect to your CapMonster Cloud account via an API key.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flhv7akhjlusrbxkbcxjl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flhv7akhjlusrbxkbcxjl.png" alt="The UI of the CapMonster Cloud extension" width="800" height="1047"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These extensions are perfect for professionals who want to &lt;strong&gt;save time and boost productivity by automating repetitive CAPTCHA challenges&lt;/strong&gt;. Below is how they work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the extension in Chrome or Firefox.&lt;/li&gt;
&lt;li&gt;Enter your CapMonster Cloud API key and ensure your account balance is not zero.&lt;/li&gt;
&lt;li&gt;The extension automatically detects CAPTCHAs, sends them to the cloud, and fills in the solution in the background.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Quite easy, isn’t it!?&lt;/p&gt;

&lt;p&gt;Also, they support developers who want to integrate them into browser automation tools like Selenium, Playwright, or Puppeteer. That's possible because the &lt;strong&gt;extensions can be downloaded as a local archive&lt;/strong&gt;, allowing configuration and programmatic integration into browser instances controlled by automation scripts.&lt;/p&gt;

&lt;p&gt;The two browser extensions also add a dedicated “CapMonster Cloud” tab in your browser’s DevTools. This tab displays details for the CAPTCHAs on the current web page, providing all the information you need to populate the body for &lt;code&gt;/createTask&lt;/code&gt; requests:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgj79fl3wwvlhhxwswqe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgj79fl3wwvlhhxwswqe.png" alt="Note the info in the “CapMonster Cloud” tab" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As of this writing, the &lt;a href="https://chromewebstore.google.com/detail/capmonster-cloud-%E2%80%94-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0/pabjfbciaedomjjfelfafejkppknjleh?roistat_visit=533078" rel="noopener noreferrer"&gt;CapMonster Cloud extension for Chrome&lt;/a&gt; has over 20k downloads and a 4.3/5 rating based on 73 reviews.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚙️ Special features&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simplifies CAPTCHA recognition for API-based solving via a “CapMonster Cloud” tab in the DevTools.&lt;/li&gt;
&lt;li&gt;Supports integration with browser automation tools like Playwright, Selenium, and Puppeteer.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.capmonster.cloud/docs/extension/autosubmit?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;Option for automatic clicking&lt;/a&gt; after CAPTCHA completion.&lt;/li&gt;
&lt;li&gt;Adds &lt;a href="https://docs.capmonster.cloud/docs/extension/text-captcha-solve?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;contextual menu options for solving text CAPTCHAs&lt;/a&gt; on the fly.&lt;/li&gt;
&lt;li&gt;Sends &lt;a href="https://docs.capmonster.cloud/docs/extension/extension-events?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;browser events&lt;/a&gt; that can be used for advanced workflows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👍 &lt;strong&gt;Pros&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automates CAPTCHA solving, saving you time.&lt;/li&gt;
&lt;li&gt;Works in the background without any user intervention.&lt;/li&gt;
&lt;li&gt;Available in both Chrome and Firefox.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👎 &lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;May have &lt;a href="https://docs.capmonster.cloud/docs/extension/known-issues#accessing-websitekey-via-shadow-dom?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;known issues&lt;/a&gt; detecting CAPTCHA keys hidden inside shadow DOMs in closed mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hands-On: Solving a CAPTCHA with CapMonster Cloud
&lt;/h2&gt;

&lt;p&gt;You know what CapMonster Cloud has to offer, but at the end of the day, what really matters is the developer experience and whether the CAPTCHA-solving solution actually works. &lt;/p&gt;

&lt;p&gt;So, let’s not waste any more time and see CapMonster Cloud in action with a real-world CAPTCHA-solving scenario, both via the API and the browser extensions!&lt;/p&gt;

&lt;h3&gt;
  
  
  Before Setting Started
&lt;/h3&gt;

&lt;p&gt;To follow along, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python installed locally.&lt;/li&gt;
&lt;li&gt;The CapMonster Cloud extension installed in your browser.&lt;/li&gt;
&lt;li&gt;A CapMonster API key.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To &lt;a href="https://docs.capmonster.cloud/docs/getting-start/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;get your CapMonster API key&lt;/a&gt;, sign up or log in, go to your dashboard, add some funds, and copy your API key:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqdk0pyux6iwzud39b0o1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqdk0pyux6iwzud39b0o1.png" alt="Copying your API key" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Store your API key in a safe place, because you’ll need it to integrate with CapMonster Cloud in both the API and browser extension walkthroughs.&lt;/p&gt;

&lt;p&gt;Specifically, I’ll show you how to solve the CAPTCHA on &lt;a href="https://www.google.com/recaptcha/api2/demo" rel="noopener noreferrer"&gt;Google’s reCAPTCHA demo page&lt;/a&gt;. There, a &lt;a href="https://developers.google.com/recaptcha/docs/versions#recaptcha_v2_im_not_a_robot_checkbox" rel="noopener noreferrer"&gt;reCAPTCHA v2 “I’m not a robot” checkbox&lt;/a&gt; prevents forms from being submitted by bots. Once the CAPTCHA is solved and the form is submitted correctly, you’ll see the “Verification Success… Hooray!” message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fddn36zby3g4w07d9kzuz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fddn36zby3g4w07d9kzuz.gif" alt="The interaction in Google's reCAPTCHA demo page" width="720" height="950"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For those not familiar with this challenge, the “I’m not a robot” checkbox is the most common version of reCAPTCHA v2. This is how it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the user is deemed human, the checkbox displays a green checkmark, allowing them to proceed without further interaction.&lt;/li&gt;
&lt;li&gt;If suspicious activity is detected, a visual challenge is presented (e.g., selecting all images containing a specific object, like a bus or traffic light).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;reCAPTCHA v2 is one of the most widely used CAPTCHA types, typically associated with form submissions and anti-bot detection. Even Google itself uses it in its search pages when it suspects bot activity:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs174g2sshdz0cmqiz78f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs174g2sshdz0cmqiz78f.png" alt="A Google reCAPTCHA v2 on a Google SERP" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s verify if CapMonster Cloud can handle it!&lt;/p&gt;

&lt;h3&gt;
  
  
  With CapMonster Cloud API
&lt;/h3&gt;

&lt;p&gt;Once you know you’re dealing with a reCAPTCHA v2, start by taking a look at the &lt;a href="https://docs.capmonster.cloud/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;Pro tip&lt;/strong&gt;: If you’re unsure which type of CAPTCHA you’re dealing with, you can check the info in the “CapMonster Cloud” tab from the browser extension, like I showed earlier. &lt;/p&gt;

&lt;p&gt;For this example, the reference is the &lt;a href="https://docs.capmonster.cloud/docs/captchas/no-captcha-task?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;&lt;code&gt;RecaptchaV2Task&lt;/code&gt; documentation page&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wf5ong5gum3vp6vccxg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wf5ong5gum3vp6vccxg.png" alt="The “RecaptchaV2Task” documentation page" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The documentation is thorough and includes all the details you need to solve this type of CAPTCHA. Don’t worry, though. If you need more guidance, the &lt;a href="https://capmonster.cloud/en/blog/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;CapMonster Cloud blog&lt;/a&gt; has step-by-step guides for all supported CAPTCHA types. For example, take a look at these two blog posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://capmonster.cloud/en/blog/how-to-bypass-recaptcha-v2-using-capmonster-cloud?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;&lt;em&gt;Solving a reCAPTCHA v2: A comprehensive guide for web scrapers&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://capmonster.cloud/en/blog/re-captcha?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;&lt;em&gt;Everything about CAPTCHA recognition&lt;/em&gt;&lt;/a&gt;
Long story short, the CapMonster Cloud team has really got you covered!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For reCAPTCHA v2, the required body for the &lt;code&gt;/createTask&lt;/code&gt; API endpoint looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"clientKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;YOUR_CAPMONSTER_CLOUD_API_KEY&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"RecaptchaV2Task"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"websiteURL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;WEB_PAGE_URL&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"websiteKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;PUBLIC_RECAPTCHA_KEY&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, you need to retrieve the &lt;code&gt;websiteKey&lt;/code&gt; from the target page. This is the site’s API key used to load the CAPTCHA and is embedded directly in the page. You can find that by exploring the DOM (e.g., &lt;a href="https://docs.capmonster.cloud/docs/captchas/no-captcha-task#how-to-find-all-required-parameters-for-task-creation?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;search for "key" or similar&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwsfi7vgkfb3hpxwcjgfs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwsfi7vgkfb3hpxwcjgfs.png" alt="Retrieving the websiteKey info" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or, to save time, you can use the “CapMonster Cloud” tab in your browser’s DevTools, as shown earlier. It’s always nice to have multiple options!&lt;/p&gt;

&lt;p&gt;In this case, the body for the &lt;code&gt;/createTask&lt;/code&gt; API call looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"clientKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;YOUR_CAPMONSTER_CLOUD_API_KEY&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"RecaptchaV2Task"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"websiteURL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"https://www.google.com/recaptcha/api2/demo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"websiteKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s literally all the info you need to solve a reCAPTCHA v2 using CapMonster Cloud. Pretty simple, right?&lt;/p&gt;

&lt;p&gt;Now, get ready to verify that the CAPTCHA-solving functionality works. For a simplified setup, I’ll use the official &lt;a href="https://github.com/ZennoLab/capmonstercloud-client-python" rel="noopener noreferrer"&gt;CapMonster Cloud Python SDK&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;First, install it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;capmonstercloudclient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you can utilize it to solve a CAPTCHA with just a few lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;capmonstercloudclient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CapMonsterClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ClientOptions&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;capmonstercloudclient.requests&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RecaptchaV2Request&lt;/span&gt;

&lt;span class="c1"&gt;# Replace it with your CapMonster Cloud API key or read it from the envs
&lt;/span&gt;&lt;span class="n"&gt;CAPMONSTER_CLOUD_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;YOUR_CAPMONSTER_CLOUD_API_KEY&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Initialize a CapMonster Cloud API client
&lt;/span&gt;    &lt;span class="n"&gt;capmonster_client_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ClientOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CAPMONSTER_CLOUD_API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;capmonster_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CapMonsterClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;capmonster_client_options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Request CAPTCHA solving from the cloud
&lt;/span&gt;    &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RecaptchaV2Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;websiteUrl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.google.com/recaptcha/api2/demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;websiteKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;capmonster_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;solve_captcha&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes, &lt;code&gt;solve_captcha()&lt;/code&gt; does all the heavy lifting for you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates a &lt;code&gt;RecaptchaV2Task&lt;/code&gt; with the given CAPTCHA details by calling the &lt;code&gt;createTask&lt;/code&gt; API endpoint.&lt;/li&gt;
&lt;li&gt;Polls the &lt;code&gt;/getTaskResult&lt;/code&gt; endpoint until the solution is ready.&lt;/li&gt;
&lt;li&gt;Returns the final token.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's precisely the same API-based CAPTCHA solving flow we discussed earlier.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;response&lt;/code&gt; will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gRecaptchaResponse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0cAFcWeA6nLpY..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;gRecaptchaResponse&lt;/code&gt; is the &lt;a href="https://developers.google.com/recaptcha/docs/verify" rel="noopener noreferrer"&gt;token you get after solving a reCAPTCHA&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To complete the flow, you need to programmatically simulate the form submission on the target page, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pip install requests
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="c1"&gt;# CAPTCHA solving code...
&lt;/span&gt;
&lt;span class="c1"&gt;# Exract the solutuon token from the response 
&lt;/span&gt;&lt;span class="n"&gt;g_recaptcha_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gRecaptchaResponse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Simulate the form submission on the target page
&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.google.com/recaptcha/api2/demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;g-recaptcha-response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;g_recaptcha_response&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Retrieve the HTML document from the response and print it
&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result will be the HTML of the verification success page, confirming that &lt;strong&gt;the CAPTCHA was solved correctly&lt;/strong&gt; (note the “Verification Success… Hooray!” message):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fumrqlownmzhvtnscx3yk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fumrqlownmzhvtnscx3yk.png" alt="The HTML output produced by the script" width="800" height="61"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mission complete! CAPTCHA solved, all with just a handful of lines of code.&lt;/p&gt;

&lt;h3&gt;
  
  
  With Browser Extensions
&lt;/h3&gt;

&lt;p&gt;Open the CapMonster Cloud extension, make sure it’s set to “On”, paste your API key into the designated field, and hit the save button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpasld7qemy2luak72tai.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpasld7qemy2luak72tai.png" alt="Setting your CapMonster Cloud API key in the browser extensio" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your account balance will also be displayed in the extension UI, so you can always check it at a glance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi40vhgw84ad1qtfjjone.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi40vhgw84ad1qtfjjone.png" alt="Your account's balance is always there" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s a nice touch since you don’t want to run out of credits mid-task. This simple feature lets you keep an eye on them.&lt;/p&gt;

&lt;p&gt;Next, make sure “ReCaptcha2” solving is enabled, set to “Click”, and that the “Auto solve” and “Auto click” options are turned on:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkzzi8c5i2gpocm334b9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkzzi8c5i2gpocm334b9.png" alt="Pressing the " width="800" height="633"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No need to go into detail about these options, as they’re pretty straightforward.&lt;/p&gt;

&lt;p&gt;Visit the target page, and you’ll see the CAPTCHA get automatically checked (note that the mouse pointer doesn’t move):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hwhcmku8qk947a4yflw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hwhcmku8qk947a4yflw.gif" alt="CapMonster Cloud Chrome extension solving the reCAPTCHA v2" width="1920" height="914"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That wasn’t magic—&lt;strong&gt;it’s just the CapMonster Cloud extension doing its job!&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Now, press the “Submit” button, and you’ll see that the CAPTCHA has been solved successfully:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm303y6zfi43ff53mzxls.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm303y6zfi43ff53mzxls.gif" alt="Manually clicking the “Submit” button after CAPTCHA solving" width="1920" height="916"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don’t want to click “Submit” manually, you can right-click, access the context menu, and select the “&lt;a href="https://docs.capmonster.cloud/docs/extension/autosubmit?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;Select element for submit&lt;/a&gt;” option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxd1u56ace8ryjzy1jkhe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxd1u56ace8ryjzy1jkhe.png" alt="Enabling the “Select element for submit” option" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This way, the extension will detect the button and handle it automatically, saving you an extra click. &lt;/p&gt;

&lt;p&gt;Amazing! There’s not much to comment here, as &lt;strong&gt;the experience is as effortless as you could hope for...&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;➡️ Want to bring this seamless CAPTCHA-solving experience into your automated browser scripts? Check out the official guide: “&lt;a href="https://docs.capmonster.cloud/docs/extension/selenium-ext?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;&lt;em&gt;Integrating the Extension into Selenium using Node.js&lt;/em&gt;&lt;/a&gt;.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing Breakdown and Competitor Comparison
&lt;/h2&gt;

&lt;p&gt;CloudMonster Cloud charges per CAPTCHA solved, following &lt;a href="https://capmonster.cloud/en#new-plans?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=antonellozanini" rel="noopener noreferrer"&gt;this pricing table&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;CAPTCHA Type&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Pricing&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;reCAPTCHA v2&lt;/td&gt;
&lt;td&gt;$0.04 per 1k images, $0.60 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reCAPTCHA v3&lt;/td&gt;
&lt;td&gt;$0.90 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reCAPTCHA Enterprise&lt;/td&gt;
&lt;td&gt;$0.04 per 1k images, $1.00 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GeeTest&lt;/td&gt;
&lt;td&gt;$1.20 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Turnstile&lt;/td&gt;
&lt;td&gt;$1.30 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Bot Challenge&lt;/td&gt;
&lt;td&gt;$1.30 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DataDome&lt;/td&gt;
&lt;td&gt;$2.20 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tencent CAPTCHA&lt;/td&gt;
&lt;td&gt;$1.60 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon WAF&lt;/td&gt;
&lt;td&gt;$1.40 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Faucet Pay&lt;/td&gt;
&lt;td&gt;$1.00 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Imperva (Incapsula)&lt;/td&gt;
&lt;td&gt;$2.00 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prosopo&lt;/td&gt;
&lt;td&gt;$1.30 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text CAPTCHA&lt;/td&gt;
&lt;td&gt;$0.30 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Temu&lt;/td&gt;
&lt;td&gt;$2.00 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yidun&lt;/td&gt;
&lt;td&gt;$1.00 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MTCaptcha&lt;/td&gt;
&lt;td&gt;$1.50 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Altcha&lt;/td&gt;
&lt;td&gt;$0.80 per 1k tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;🚨 &lt;strong&gt;Important&lt;/strong&gt;: Proxy traffic used by the Cloud solver for non-proxyless CAPTCHA tasks is included in the above prices.&lt;/p&gt;

&lt;p&gt;In other words, to make things simple, every time you use CloudMonster Cloud to solve a reCAPTCHA v2, you pay $0.0006 per solution token (equivalent to $0.60 per 1,000 tokens). Put differently, &lt;strong&gt;$1 covers roughly 1,666 reCAPTCHA v2 challenges&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;ℹ️ &lt;strong&gt;Note&lt;/strong&gt;: Payment options are flexible and include PayPal, MasterCard, Visa, Wire transfers, cryptocurrencies, UnionPay, and MIR (payments in Russian rubles). So there’s really an option for everyone.&lt;/p&gt;

&lt;p&gt;Based on my experience in the web scraping industry, these prices are competitive—among the best compared to other top CAPTCHA-solving providers. Considering that &lt;strong&gt;I achieved a 99% success rate in my tests&lt;/strong&gt;, I believe CloudMonster Cloud is well worth the money you’re paying!&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Verdict
&lt;/h2&gt;

&lt;p&gt;In this review, I had the chance to try CapMonster Cloud in depth and see for myself whether it really is one of the best CAPTCHA-solving solutions available on the web.&lt;/p&gt;

&lt;p&gt;The experience was fantastic. That's true for both developers using their API and SDKs, and regular users leveraging the browser extension to save time on CAPTCHA-heavy tasks. In both cases, the underlying AI-solving system doesn’t disappoint. &lt;/p&gt;

&lt;p&gt;I’ve personally seen it work its magic a few times—like automatically selecting all the traffic lights in a reCAPTCHA challenge—saving a lot of manual effort. I imagine the same magic is applied in the cloud when solving CAPTCHAs via API&lt;/p&gt;

&lt;p&gt;There are a few extra features worth noting, too. The CAPTCHA recognition tool available in the extension is a huge time-saver for developers. I also appreciated that the extensions are provided as downloadable archives, making it easy to integrate into browser automation setups.&lt;/p&gt;

&lt;p&gt;Considering the competitive pricing, pay-for-success model, the long list of features, and the extensive documentation and blog posts, I can confidently say that CapMonster Cloud is one of the best CAPTCHA-solving services out there—and hands down the best I’ve personally tried!&lt;/p&gt;

&lt;p&gt;Review score: &lt;strong&gt;9.6/10&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwvop1fostbh0hf3ygja.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwvop1fostbh0hf3ygja.png" alt="Final verdict" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>review</category>
      <category>captcha</category>
      <category>python</category>
    </item>
    <item>
      <title>Advanced JavaScript Asynchronous With Async.js</title>
      <dc:creator>Antonello Zanini</dc:creator>
      <pubDate>Sat, 07 Jun 2025 12:11:10 +0000</pubDate>
      <link>https://dev.to/antozanini/advanced-javascript-asynchronous-with-asyncjs-3gmm</link>
      <guid>https://dev.to/antozanini/advanced-javascript-asynchronous-with-asyncjs-3gmm</guid>
      <description>&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous" rel="noopener noreferrer"&gt;Asynchronous JavaScript&lt;/a&gt; has changed the way we approach backend and frontend development. Still, writing asynchronous code remains complex, especially considering that many native JavaScript functions do not work well with async operations. This is exactly where the Async.js library comes in!&lt;/p&gt;

&lt;p&gt;As a set of powerful utility functions, Async provides everything you need to handle complex JavaScript async scenarios in just a few lines of code. Given how common these challenges are, it is no surprise that the library has over 50 million weekly downloads!&lt;/p&gt;

&lt;p&gt;Let’s learn why asynchronous programming is challenging in JavaScript, how Async.js aims to resolve these issues, what features the library offers, and how to use it in two real-world examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  JavaScript Asynchronous Development: Benefits and Challenges
&lt;/h2&gt;

&lt;p&gt;The introduction of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" rel="noopener noreferrer"&gt;&lt;code&gt;Promise&lt;/code&gt;&lt;/a&gt;s and the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function" rel="noopener noreferrer"&gt;&lt;code&gt;async&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await" rel="noopener noreferrer"&gt;&lt;code&gt;await&lt;/code&gt;&lt;/a&gt; constructs in JavaScript has revolutionized web development. Handling asynchronous operations is simpler than ever, as you can now write async code that looks and reads like synchronous code. For more information, take a look at our article on &lt;a href="https://blog.openreplay.com/javascript-async-programming-tips-tricks-and-gotchas/" rel="noopener noreferrer"&gt;JavaScript asynchronous programming tips, tricks, and gotchas&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Among the main benefits of using &lt;code&gt;Promise&lt;/code&gt;s along with &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; are improved code readability and simpler error handling through &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch" rel="noopener noreferrer"&gt;&lt;code&gt;try...catch&lt;/code&gt;&lt;/a&gt; blocks. However, challenges do exist—such as managing complex asynchronous flows or iterating over async tasks. &lt;/p&gt;

&lt;p&gt;Sure, some of those issues can be mitigated with &lt;a href="https://blog.openreplay.com/best-practices-for-async-programming-in-javascript/" rel="noopener noreferrer"&gt;best practices for async programming in JavaScript&lt;/a&gt;, such as reducing the callback hell. Yet, the best solution might be to use a dedicated library like &lt;a href="http://caolan.github.io/async/" rel="noopener noreferrer"&gt;Async&lt;/a&gt;, which provides dozens of functions to streamline async logic. &lt;/p&gt;

&lt;p&gt;Learn more about Async in the chapter below!&lt;/p&gt;

&lt;h2&gt;
  
  
  Async.js: A Library For Simplified Asynchronous JavaScript
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://caolan.github.io/async/v3/" rel="noopener noreferrer"&gt;Async&lt;/a&gt;, also known as Async.js, is a utility module that provides powerful functions for working with asynchronous JavaScript. It supports Node.js and the browser, for simplified asynchronous programming in both the backend and the frontend.&lt;/p&gt;

&lt;p&gt;Async includes around 70 functions for dealing with asynchronous collections, control flow, and other useful operations—such as logging and memorization. The &lt;a href="https://github.com/caolan/async" rel="noopener noreferrer"&gt;GitHub repository of the project&lt;/a&gt; boasts over 28k stars, and the npm package has almost 55 million weekly downlands.&lt;/p&gt;

&lt;p&gt;You can install Async via the &lt;a href="https://www.npmjs.com/package/async" rel="noopener noreferrer"&gt;&lt;code&gt;async&lt;/code&gt;&lt;/a&gt; package with the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;async
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you want the pure ESM version of Async, install &lt;a href="https://www.npmjs.com/package/async-es" rel="noopener noreferrer"&gt;&lt;code&gt;async-es&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;async-es
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are a CommonJS user, you can now import &lt;code&gt;async&lt;/code&gt; in your code with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you are an ESM user, import it as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Functions Offered By Async.js
&lt;/h2&gt;

&lt;p&gt;Time to explore the most useful methods provided by Async, categorized as in &lt;a href="https://caolan.github.io/async/v3/docs.html" rel="noopener noreferrer"&gt;the official documentation&lt;/a&gt; in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Collections&lt;/strong&gt;: Functions for manipulating collections, such as arrays and objects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control Flow&lt;/strong&gt;: Functions for controlling the flow through a script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Utilities&lt;/strong&gt;: Utility functions for JavaScript async development.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that each Async.js function accepts an optional callback as the last argument. If provided, the callback is executed when the function completes or if an error occurs. If the callback is omitted, the Async function returns a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise" rel="noopener noreferrer"&gt;&lt;code&gt;Promise&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example, you can use the Async &lt;a href="https://caolan.github.io/async/v3/docs.html#map" rel="noopener noreferrer"&gt;&lt;code&gt;map()&lt;/code&gt;&lt;/a&gt; function with a callback as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// output: [2, 4, 6]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;map()&lt;/code&gt; from Async.js concurrently applies an async transformation function to each item in a collection. It processes each item in parallel and returns a new array of results, preserving the original order of the items.&lt;/p&gt;

&lt;p&gt;Or, equivalently, without the callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// output: [2, 4, 6]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code can also be re-written to use the &lt;code&gt;asnyc/await&lt;/code&gt; syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// output: [2, 4, 6]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Collections
&lt;/h3&gt;

&lt;p&gt;Async provides more than 35 functions for handling JavaScript collections concurrently. Here, we will focus on the 5 most popular ones, but you can find them all &lt;a href="https://caolan.github.io/async/v3/docs.html#collections" rel="noopener noreferrer"&gt;in the documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note that for most functions in this category, there are two additional special versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;functionNameLimit()&lt;/code&gt;: Accepts a &lt;code&gt;limit&lt;/code&gt; argument, and runs a maximum of &lt;code&gt;limit&lt;/code&gt; async operations at a time.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;functionNameSeries()&lt;/code&gt;: Runs only a single async operation at a time to respect the order of the collection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For instance, &lt;code&gt;map()&lt;/code&gt; has the &lt;a href="https://caolan.github.io/async/v3/docs.html#mapLimit" rel="noopener noreferrer"&gt;&lt;code&gt;mapLimit()&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://caolan.github.io/async/v3/docs.html#mapSeries" rel="noopener noreferrer"&gt;&lt;code&gt;mapSeries()&lt;/code&gt;&lt;/a&gt; versions.&lt;/p&gt;

&lt;h4&gt;
  
  
  detect
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://caolan.github.io/async/v3/docs.html#detect" rel="noopener noreferrer"&gt;&lt;code&gt;detect()&lt;/code&gt;&lt;/a&gt; returns the first item in a collection that meets an asynchronous condition. The test function is applied to the collection in parallel, so the result may not be the first item from left to right. If order matters, use &lt;a href="https://caolan.github.io/async/v3/docs.html#detectSeries" rel="noopener noreferrer"&gt;&lt;code&gt;detectSeries()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// sample async test to run on the current item&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// potential output: 2 (or 4)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  each
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://caolan.github.io/async/v3/docs.html#each" rel="noopener noreferrer"&gt;&lt;code&gt;each()&lt;/code&gt;&lt;/a&gt; simultaneously applies a given function to each item in a collection. If any of the function calls return an error, the main callback is immediately triggered with that error.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;each&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async/each&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// sample async operation to execute on the current item&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;All items processed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  every
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://caolan.github.io/async/v3/docs.html#every" rel="noopener noreferrer"&gt;&lt;code&gt;every()&lt;/code&gt;&lt;/a&gt; checks if all items in a collection meet an asynchronous condition. If any of the items fail the condition, it immediately calls the main callback with &lt;code&gt;false&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;every&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async/every&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// sample async test to run on the current item&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// output: true (as all numbers are even)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  filter
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://caolan.github.io/async/v3/docs.html#filter" rel="noopener noreferrer"&gt;&lt;code&gt;filter()&lt;/code&gt;&lt;/a&gt; returns a new array with items from the collection that pass an asynchronous truth test, maintaining the original order.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async/filter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// sample async test to run on the current item&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// output: [2, 4, 6]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  reduce
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://caolan.github.io/async/v3/docs.html#reduce" rel="noopener noreferrer"&gt;&lt;code&gt;reduce()&lt;/code&gt;&lt;/a&gt; combines items in a collection into a single value by applying an async function to each item, one at a time, using a given starting value.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;reduce&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async/reduce&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// sample async function to combine items with&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// output: 10 (1 + 2 + 3 + 4)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Control Flow
&lt;/h3&gt;

&lt;p&gt;Async offers over 25 functions for simplifying asynchronous control flow. Below, we will focus on the 3 functions. Explore them all &lt;a href="https://caolan.github.io/async/v3/docs.html#controlflow" rel="noopener noreferrer"&gt;in the documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  parallel
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://caolan.github.io/async/v3/docs.html#parallel" rel="noopener noreferrer"&gt;&lt;code&gt;parallel()&lt;/code&gt;&lt;/a&gt; runs multiple asynchronous tasks at the same time. Each task starts immediately without waiting for others to complete. If any task returns an error, the main callback is triggered with that error. When all tasks finish, the results are returned in an array (or an object if an object is used for tasks).&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;parallel&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async/parallel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// sample parallel tasks&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Task 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Task 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// output: ["Task 1", "Task 2"]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  queue
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://caolan.github.io/async/v3/docs.html#queue" rel="noopener noreferrer"&gt;&lt;code&gt;queue()&lt;/code&gt;&lt;/a&gt; creates a queue to handle tasks with a set concurrency limit. Tasks are processed in parallel up to the limit. New tasks wait in the queue until a worker becomes available. When each task completes, the callback is called.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async/queue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taskQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Processing &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// sample async task&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// limit concurrency to 2&lt;/span&gt;

&lt;span class="c1"&gt;// loading all tasks into the queue&lt;/span&gt;
&lt;span class="nx"&gt;taskQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Task 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Task 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Task 3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]);&lt;/span&gt;

&lt;span class="c1"&gt;// executing the tasks in the queue&lt;/span&gt;
&lt;span class="nx"&gt;taskQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drain&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;All tasks completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  series
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://caolan.github.io/async/v3/docs.html#series" rel="noopener noreferrer"&gt;&lt;code&gt;series&lt;/code&gt;&lt;/a&gt; runs tasks one after the other, waiting for each to complete before starting the next. If a task fails, no further tasks are run, and the main callback is called with the error. When all tasks are complete, the results are returned in an array (or an object if an object is used).&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;series&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async/series&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;series&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// sample async tasks to run sequentially&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;First Task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Second Task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// output: ["First Task", "Second Task"]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Utilities
&lt;/h3&gt;

&lt;p&gt;Async offers a few utility functions to make it easier to handle asynchronous tasks. We will analyze only 3 of them, but you can find them all &lt;a href="https://caolan.github.io/async/v3/docs.html#utils" rel="noopener noreferrer"&gt;in the documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  log
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://caolan.github.io/async/v3/docs.html#log" rel="noopener noreferrer"&gt;&lt;code&gt;log()&lt;/code&gt;&lt;/a&gt; prints the result of an async function directly to the console. If the async function returns multiple values, each value is logged in order. This function only works in Node.js and in frontend applications that support &lt;code&gt;console.log()&lt;/code&gt; and &lt;code&gt;console.error()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async/log&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// sample async task&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;asyncTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Result 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Result 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;asyncTask&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="c1"&gt;// output in the console: &lt;/span&gt;
&lt;span class="c1"&gt;// "Result 1" &lt;/span&gt;
&lt;span class="c1"&gt;// "Result 2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  memoize
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://caolan.github.io/async/v3/docs.html#memoize" rel="noopener noreferrer"&gt;&lt;code&gt;memoize()&lt;/code&gt;&lt;/a&gt; caches the result of an async function. This way, repeated calls with the same arguments use the cached result instead of running the function again. An optional hash function can be provided to customize how arguments are used as keys in the cache. &lt;/p&gt;

&lt;p&gt;If you are not familiar with memoization, read our article on &lt;a href="https://blog.openreplay.com/forever-functional-memoizing-functions-for-performance/" rel="noopener noreferrer"&gt;memoizating functions for performance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;memoize&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async/memoize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// sample async function&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slowFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;memoizedFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;memoize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slowFunction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;memoizedFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// takes 100ms, logs "10"&lt;/span&gt;
&lt;span class="nf"&gt;memoizedFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// cached, logs "10" immediately&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  timeout
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://caolan.github.io/async/v3/docs.html#timeout" rel="noopener noreferrer"&gt;&lt;code&gt;timeout()&lt;/code&gt;&lt;/a&gt; sets a maximum time limit on an async function. If the function does not finish within the specified time, it returns a &lt;a href="https://nodejs.org/api/errors.html" rel="noopener noreferrer"&gt;&lt;code&gt;ETIMEDOUT&lt;/code&gt;&lt;/a&gt; error instead.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async/timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// sample async task&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;longTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Done!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// apply the timeout function&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;limitedTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;longTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;limitedTask&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// output: "Callback function timed out."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Async.js in Action
&lt;/h2&gt;

&lt;p&gt;Now that you have an idea of what Async.js has to offer, you are ready to see it in action in real-world scenarios!&lt;/p&gt;

&lt;h3&gt;
  
  
  Async Loop Functions
&lt;/h3&gt;

&lt;p&gt;Functions like &lt;code&gt;forEach()&lt;/code&gt;, &lt;code&gt;reduce()&lt;/code&gt;, &lt;code&gt;map()&lt;/code&gt;, and &lt;code&gt;filter()&lt;/code&gt; are among the most common in JavaScript development. Still, challenges arise when using these methods with asynchronous calls or promises. In this case, results may not be what you would expect. For more information, follow the &lt;a href="https://medium.com/dailyjs/async-loops-and-why-they-fail-part-1-6909a7d134f2" rel="noopener noreferrer"&gt;4-part guide on async loops, and why they fail&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example, see this async function to simulate an API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;asyncCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeToWait&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dataToReturn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataToReturn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;timeToWait&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns a &lt;code&gt;Promise&lt;/code&gt; that resolves after a specified delay, just like what would happen when calling an endpoint.&lt;/p&gt;

&lt;p&gt;Now, use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach" rel="noopener noreferrer"&gt;&lt;code&gt;forEach()&lt;/code&gt;&lt;/a&gt; to call the above function on each element in an array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;START using forEach&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;asyncCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`data #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;END using forEach&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the code and the output will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;START using forEach
END using forEach
data #1
data #2
data #3
data #5
data #8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, &lt;code&gt;forEach()&lt;/code&gt; ends immediately, while the results from &lt;code&gt;asyncCall()&lt;/code&gt; are logged only afterward. This occurs because &lt;code&gt;forEach()&lt;/code&gt; does not wait for the &lt;code&gt;Promise&lt;/code&gt;s to resolve. In particular, it launches all asynchronous tasks, but it returns immediately. Thus, the async logic defined in the &lt;code&gt;async&lt;/code&gt; callbacks passed to &lt;code&gt;forEach()&lt;/code&gt; executes only after &lt;code&gt;forEach()&lt;/code&gt; has returned.&lt;/p&gt;

&lt;p&gt;To properly handle asynchronous calls in an iteration, you can use &lt;code&gt;Promise&lt;/code&gt;s with a custom &lt;code&gt;asyncForEach&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;asyncForEach&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;START using asyncForEach&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;asyncForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;asyncCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`data #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;END using asyncForEach&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks to &lt;code&gt;Promise&lt;/code&gt; chaining with &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce" rel="noopener noreferrer"&gt;&lt;code&gt;array.reduce()&lt;/code&gt;&lt;/a&gt;, you can control the flow of execution and ensure that each async operation completes in series.&lt;/p&gt;

&lt;p&gt;With this approach, the output will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;START using asyncForEach
data #1
data #2
data #3
data #5
data #8
END using asyncForEach
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While that solution works as desired, it introduces complexity and boilerplate code. This is where the &lt;code&gt;Async.js&lt;/code&gt; library comes into play!&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://caolan.github.io/async/v3/docs.html#eachSeries" rel="noopener noreferrer"&gt;&lt;code&gt;eachSeries()&lt;/code&gt;&lt;/a&gt; function from &lt;code&gt;Async.js&lt;/code&gt; is designed to execute asynchronous tasks one at a time. Here is how you can rewrite the iteration logic with using &lt;code&gt;eachSeries()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// function asyncCall(timeToWait, dataToReturn) { ... }&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;START using Async.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eachSeries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;asyncCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`data #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// signal completion of the current iteration&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;END using Async.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new logic is much easier to read and maintain, eliminating the cumbersome &lt;code&gt;Promise&lt;/code&gt; management introduced in the previous solution.&lt;/p&gt;

&lt;p&gt;Once again, the output will be the expected one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;START using Async.js
data #1
data #2
data #3
data #5
data #8
END using Async.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wonderful! This is the power of Async.js.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queuing Async Requests
&lt;/h3&gt;

&lt;p&gt;To understand the capabilities of Async, let's consider another scenario.&lt;/p&gt;

&lt;p&gt;Suppose you want to perform web scraping on a site with many pages. If you do not know what that entails, follow our guide on &lt;a href="https://blog.openreplay.com/web-scraping-with-node-js-and-cheerio/" rel="noopener noreferrer"&gt;web scraping in Node.js&lt;/a&gt;. To speed up the process, the best practice is to send multiple simultaneous requests. At the same time, you do not want to overload the server with too many requests. So, you need to limit the number of concurrent requests in some way.&lt;/p&gt;

&lt;p&gt;Now, imagine that you have a list of URLs that you want to scrape for data from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/page1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/page2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/page3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/page4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/page5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/page6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/page7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/page8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/page9&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/page10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To avoid flooding the site’s server, you want to have no more than 5 pending requests at the same time.&lt;/p&gt;

&lt;p&gt;Implementing the desired behavior with vanilla JavaScript involves custom queuing logic. Instead, Async.js simplifies the process with its &lt;code&gt;queue()&lt;/code&gt; method. This enables you to initiate new requests as soon as previous ones complete, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;async&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// async function to scrape data from a URL&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scrapeUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// scraping logic...&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// list of URLs to scrape&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;// omitted for brevity...&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// create a queue with a concurrency limit of 5&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scrapeUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// add the URLs to the queue&lt;/span&gt;
&lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to process &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// launch the scraping logic&lt;/span&gt;
&lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drain&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;All URLs have been scraped successfully!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When using &lt;code&gt;queue()&lt;/code&gt; in Async, you first need to register all async tasks with the &lt;code&gt;push()&lt;/code&gt; method. Then, you can execute them all simultaneously with the specified concurrency limit with &lt;code&gt;drain()&lt;/code&gt;. Thanks to how a queue works, if the first request completes before the other four are still pending, the scraper will immediately send another request. &lt;/p&gt;

&lt;p&gt;With Async, handling async queues has never been easier!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Async is a popular JavaScript library that addresses the limitations of native JavaScript's asynchronous programming features. For instance, it provides specialized versions of functions like &lt;code&gt;map()&lt;/code&gt;, &lt;code&gt;each()&lt;/code&gt;, &lt;code&gt;every()&lt;/code&gt;, and &lt;code&gt;reduce()&lt;/code&gt; that work well with asynchronous functions.&lt;/p&gt;

&lt;p&gt;Here, you saw why async programming in JavaScript is not always a piece of cake. While you could address most issues with custom code, using a dedicated library like Async makes everything easier.&lt;/p&gt;

&lt;p&gt;As shown above, integrating Async functions into your code is simple, whether you prefer the callback style or Promise-based syntax!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Map a JSON Column in Spring Boot With JPA 3 and Hibernate 6</title>
      <dc:creator>Antonello Zanini</dc:creator>
      <pubDate>Thu, 24 Apr 2025 07:31:54 +0000</pubDate>
      <link>https://dev.to/antozanini/how-to-map-a-json-column-in-spring-boot-with-jpa-3-and-hibernate-6-5gd5</link>
      <guid>https://dev.to/antozanini/how-to-map-a-json-column-in-spring-boot-with-jpa-3-and-hibernate-6-5gd5</guid>
      <description>&lt;p&gt;Building a RESTful application in Spring Boot that reads data from a database requires the definition of some ORM mappings. Even though this is generally a boring task, it is crucial for the proper functioning of the web application. &lt;/p&gt;

&lt;p&gt;The ORM used by Spring Boot 3 with JPA 3 is Hibernate 6, which changed the way some column types need to be mapped. This is the case of JSON data type. In detail, you can now map a JSON column in Hibernate to a &lt;code&gt;String&lt;/code&gt;, a &lt;code&gt;Map&lt;/code&gt;, or a custom object.  &lt;/p&gt;

&lt;p&gt;Let’s learn the different approaches you can follow for JSON mapping in Hibernate 6 and JPA 3!&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;This article refers to Spring Boot 3 with &lt;a href="https://jakarta.ee/specifications/persistence/3.1/" rel="noopener noreferrer"&gt;JPA 3.1&lt;/a&gt; and &lt;a href="https://in.relation.to/2023/04/14/hibernate-orm-621-final/" rel="noopener noreferrer"&gt;Hibernate 6.2&lt;/a&gt;. If you have a Spring Boot 2 project, follow the &lt;a href="https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide" rel="noopener noreferrer"&gt;official guide&lt;/a&gt; to migrate it to version 3.0.  Keep in mind that Spring Boot JPA 3 uses Hibernate 6 by default. &lt;/p&gt;

&lt;p&gt;After setting up a Spring Boot 3 project, you will need a database involving JSON data to connect to. Any DBMS technologies supporting a data type for JSON will do. For example, suppose you have a &lt;code&gt;configs&lt;/code&gt; table in a PostgreSQL database defined by the following two columns:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsf117c87bl0cxre0c11v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsf117c87bl0cxre0c11v.png" alt="The configs table" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note the &lt;code&gt;data&lt;/code&gt; columns of type &lt;code&gt;jsonb&lt;/code&gt;. That is the most complete data type for storing JSON data in PostgreSQL. Check out our guide to learn more about &lt;a href="https://www.dbvis.com/thetable/json-vs-jsonb-in-postgresql-a-complete-comparison/" rel="noopener noreferrer"&gt;JSON vs JSONB in Postgres&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In detail, take a look at the query below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff2vu2u7yem5krurvw6rg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff2vu2u7yem5krurvw6rg.png" alt="Executing a SELECT query in DbVisualizer" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the JSON data stored in a record of the &lt;code&gt;config&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"graphics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1920x1080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"quality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"anti_aliasing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"gameplay"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"difficulty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"normal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"achievements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"tutorial_passed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chapter_1_mastered"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have everything you need to get started. There are three different approaches to JSON mapping in Hibernate. Time to explore them all!&lt;/p&gt;

&lt;h2&gt;
  
  
  Method #1: Map a JSON Column to a String
&lt;/h2&gt;

&lt;p&gt;The easiest way to map a JSON column in Hibernate is to use a &lt;code&gt;String&lt;/code&gt;. This is a straightforward and simple approach, but you should only use it when dealing with read-only JSON data. The reason is that changing and exploring the JSON data read at the application layer will involve cumbersome operations on a raw string.&lt;/p&gt;

&lt;p&gt;You can map a JSON column to a &lt;code&gt;String&lt;/code&gt; in a JPA entity as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.dbvis.demo.entities&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jakarta.persistence.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.hibernate.annotations.JdbcTypeCode&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.hibernate.type.SqlTypes&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Entity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"configs"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AUTO&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@JdbcTypeCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SqlTypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// getters and setters omitted for brevity...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, to activate JSON mapping in Hibernate you only need to annotate your column attribute with &lt;a href="https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#basic-mapping-json" rel="noopener noreferrer"&gt;&lt;code&gt;@JdbcTypeCode(SqlTypes.JSON)&lt;/code&gt;&lt;/a&gt;. Hibernate will now use &lt;a href="https://github.com/FasterXML/jackson" rel="noopener noreferrer"&gt;Jackson&lt;/a&gt;, the standard JSON library coming with Spring Boot, to serialize and deserialize the attribute’s value to the type specified. In particular, it will convert &lt;code&gt;data&lt;/code&gt; to a String when reading and transform it to JSON when writing.&lt;/p&gt;

&lt;p&gt;Note that &lt;a href="https://docs.jboss.org/hibernate/orm/6.2/javadocs/org/hibernate/type/SqlTypes.html#JSON" rel="noopener noreferrer"&gt;&lt;code&gt;SqlTypes.JSON&lt;/code&gt;&lt;/a&gt; represent the generic SQL type JSON. Specifically, the default JSON data type used behind the scene changes according to the RDBMS dialect configured. For PostgreSQL, that is &lt;code&gt;JSONB&lt;/code&gt; data type.&lt;/p&gt;

&lt;p&gt;Now, assume you have a &lt;code&gt;/api/configs/1&lt;/code&gt; endpoint that returns the first record of the &lt;code&gt;config&lt;/code&gt; table. Call it and you would get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;gameplay&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;difficulty&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;normal&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;achievements&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: [&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;tutorial_passed&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;chapter_1_mastered&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;]}, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;graphics&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: {&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;quality&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;high&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;resolution&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;1920x1080&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;anti_aliasing&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true}}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As expected, the &lt;code&gt;data&lt;/code&gt; field in the JSON response produced by Spring Boot is a string containing the JSON content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to implement&lt;/li&gt;
&lt;li&gt;JSON schema agnostic &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basically limited to read-only JSON columns&lt;/li&gt;
&lt;li&gt;JSON content is returned as a raw string&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Method #2: Map a JSON Column to a Java Map
&lt;/h2&gt;

&lt;p&gt;Another possible solution supported by Hibernate is to map the JSON content to a &lt;a href="https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/util/Map.html" rel="noopener noreferrer"&gt;&lt;code&gt;Map&amp;lt;String, String&amp;gt;&lt;/code&gt;&lt;/a&gt; object. Take a look at the JPA entity below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.dbvis.demo.entities&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jakarta.persistence.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.hibernate.annotations.JdbcTypeCode&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.hibernate.type.SqlTypes&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Map&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Entity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"configs"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AUTO&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@JdbcTypeCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SqlTypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// getters and setters omitted for brevity...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the type declared on the &lt;code&gt;data&lt;/code&gt; field. When retrieving JSON data, Spring Boot will now instantiate a &lt;code&gt;Map&lt;/code&gt; object containing the JSON values in &lt;code&gt;&amp;lt;key, value&amp;gt;&lt;/code&gt; string pairs. Similarly, Jackson will serialize the &lt;code&gt;Map&lt;/code&gt; object associated with the &lt;code&gt;data&lt;/code&gt; field to JSON when writing a new record to the database. &lt;/p&gt;

&lt;p&gt;If you now call the &lt;code&gt;/api/configs/1&lt;/code&gt; API, you will get the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That occurs because this approach to JSON mapping in JPA does not support JSON values involving arrays or nested objects. Thus, it only works with flat JSON data consisting of primitive fields.&lt;/p&gt;

&lt;p&gt;Assume your &lt;code&gt;data&lt;/code&gt; column stores the following flat JSON data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"difficulty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"normal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"quality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1920x1080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"anti_aliasing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, the endpoint will return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"quality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"difficulty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"normal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1920x1080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"anti_aliasing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the &lt;code&gt;data&lt;/code&gt; column containing JSON data was correctly serialized to JSON by Jackson. With this approach, the &lt;code&gt;data&lt;/code&gt; field in the JSON response generated by the server is a proper object and no longer a simple string.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON content gets serialized as expected&lt;/li&gt;
&lt;li&gt;Support for CRUD operation on the JSON content via the &lt;code&gt;Map&lt;/code&gt; API &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only supports flat JSON data involving primitive values&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Method #3: Map a JSON Column to an Object
&lt;/h2&gt;

&lt;p&gt;To map a JSON column to a custom Java object, you only have to specify it as the field type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;jakarta.persistence.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.hibernate.annotations.JdbcTypeCode&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.hibernate.type.SqlTypes&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// import the custom data type to map the JSON field to&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.dbvis.demo.data.ConfigData&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Entity&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"configs"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AUTO&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@JdbcTypeCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SqlTypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ConfigData&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// getters and setters omitted for brevity...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When querying some data, Jackson will now create a &lt;code&gt;ConfigData&lt;/code&gt; object mapping the JSON content retrieved from the database. If you instead want to write JSON data, you will have to instantiate a new &lt;code&gt;ConfigData&lt;/code&gt; object with the desired info. Jackson will automatically convert it to JSON for you.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ConfigData&lt;/code&gt; is nothing more than a &lt;a href="https://en.wikipedia.org/wiki/Plain_old_Java_object" rel="noopener noreferrer"&gt;POJO&lt;/a&gt; for data mapping. Defining POJO classes is a tedious process that involves boilerplate code. For this reason, you should use one of the many JSON to POJO converters available online.&lt;/p&gt;

&lt;p&gt;In detail, this is what &lt;code&gt;ConfigData&lt;/code&gt; looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.dbvis.demo.data&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConfigData&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ConfigDataGameplay&lt;/span&gt; &lt;span class="n"&gt;gameplay&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ConfigDataGraphics&lt;/span&gt; &lt;span class="n"&gt;graphics&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// getters and setters...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the JSON data stored in the database contains two nested objects, you need two custom Java classes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ConfigDataGameplay&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.dbvis.demo.data&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.List&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConfigDataGameplay&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;difficulty&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;achievements&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// getters and setters...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ConfigDataGraphics&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.dbvis.demo.data&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.fasterxml.jackson.annotation.JsonProperty&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ConfigDataGraphics&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;quality&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;resolution&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nd"&gt;@JsonProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"anti_aliasing"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;antiAliasing&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// getters and setters...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note the use of &lt;a href="https://fasterxml.github.io/jackson-annotations/javadoc/2.13/com/fasterxml/jackson/annotation/JsonProperty.html" rel="noopener noreferrer"&gt;&lt;code&gt;@JsonProperty&lt;/code&gt;&lt;/a&gt; Jackson annotation to specify custom mapping behavior. Do not forget that Hibernate will use Jackson for serialization and deserialization.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/api/configs/1&lt;/code&gt; endpoint will now return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gameplay"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"difficulty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"normal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"achievements"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"tutorial_passed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"chapter_1_mastered"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"graphics"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"quality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"resolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1920x1080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"anti_aliasing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et voilà! This is exactly the JSON data contained in the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Support for any type of JSON data&lt;/li&gt;
&lt;li&gt;Flexible and customizable&lt;/li&gt;
&lt;li&gt;Enables typed JSON data management at the application level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defining mapping POJO classes is cumbersome&lt;/li&gt;
&lt;li&gt;Instantiating POJOs has an impact on performance&lt;/li&gt;
&lt;li&gt;JSON data in the database must follow the schema defined by the POJOs &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Explore JSON Data With DbVisualizer
&lt;/h2&gt;

&lt;p&gt;As seen above, dealing with JSON with Hibernate is not that complex. At the same time, you may need a tool to explore the JSON data stored in your database. This is where an advanced database client such as &lt;a href="https://www.dbvis.com/" rel="noopener noreferrer"&gt;DbVisualizer&lt;/a&gt; comes into play!&lt;/p&gt;

&lt;p&gt;DbVisualizer is a powerful database management tool that &lt;a href="https://www.dbvis.com/supported-databases/" rel="noopener noreferrer"&gt;supports a wide range of databases&lt;/a&gt;, such as MySQL, Oracle, Microsoft SQL server, PostgreSQL, and more. It comes with advanced data exploration features that allow you to visually see, edit, create, and delete data. Also, DbVisualizer has in-depth support for SQL data types, including JSON types.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnmejk7nk931f8j7tig6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnmejk7nk931f8j7tig6.png" alt="See the DbVisulizer support for JSON data types" width="800" height="714"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are the steps to explore JSON data using DbVisualizer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Connect to the database&lt;/strong&gt;: Follow the wizard to set up a connection to your database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigate to the desired table&lt;/strong&gt;: Once you have connected to the database, use the “Databases” dropdown on the left to navigate to the table that contains the JSON data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzx7oigjtidsrp1sxzwf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzx7oigjtidsrp1sxzwf.png" alt="Navigating to the configs table" width="460" height="747"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;View the JSON data&lt;/strong&gt;: Once you have navigated to the table, open it in a new tab, reach the “Data” tab, and double-click on the JSON data contained in some row. DbVisualizer allows you to view and edit it as a formatted JSON.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzuz66ozxoxvribktro1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzuz66ozxoxvribktro1.png" alt="Viewing the JSON data" width="555" height="678"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With DbVisualizer, dealing with JSON data has never been easier!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this tutorial, you learned how to map JSON columns in Spring Boot. In detail, you saw three different approaches to represent JSON in Hibernate 6 and JPA 3. You can now map a JSON column to a &lt;code&gt;String&lt;/code&gt;, a &lt;code&gt;Map&lt;/code&gt;, and a custom Java object.&lt;/p&gt;

&lt;p&gt;Also, you learned how important it is to have a tool to explore JSON data visually. Luckily, there is DbVisualizer, a fully-featured database client that comes with advanced data exploration capabilities. &lt;a href="https://www.dbvis.com/download/" rel="noopener noreferrer"&gt;Try DbVisualizer for free!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;Let’s now answer some common questions about JSON mapping in Hibernate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is JSON column mapping possible in Spring Boot?
&lt;/h3&gt;

&lt;p&gt;Yes, thanks to JPA and Hibernate, you can map JSON columns in Spring Boot. In particular, JPA 3 and Hibernate 6 have made JSON mapping easier and more straightforward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it possible to define custom mapping behavior for JSON columns in Hibernate?
&lt;/h3&gt;

&lt;p&gt;Yes, you can specify custom mapping behavior thanks to the annotations offered by Jackson or the JSON library configured in Hibernate by overriding the &lt;code&gt;hibernate.type.json_format_mapper&lt;/code&gt; setting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it possible to map JSON columns as non-string in Hibernate?
&lt;/h3&gt;

&lt;p&gt;Yes, you can map JSON columns in Hibernate to different types. All you have to is specify the desired type on the &lt;code&gt;@Column&lt;/code&gt; field. In detail, Hibernate supports &lt;code&gt;Map&lt;/code&gt; and custom POJO type for JSON mapping.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I map nested JSON fields using JPA and Hibernate in Spring Boot?
&lt;/h3&gt;

&lt;p&gt;You can map nested JSON fields in JPA and Hibernate by associating your JSON field in the JPA entity to a custom POJO class. This class can then involve fields of custom types. Basically, you have to break down your nested JSON structure into several data-mapping POJOs.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the benefits of mapping JSON columns in JPA?
&lt;/h3&gt;

&lt;p&gt;Mapping JSON columns in JPA allows you to perform CRUD operations on those fields at the application layer. Also, if mapping the JSON columns correctly, JPA will automatically serialize and deserialize it for you.&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>json</category>
      <category>sql</category>
    </item>
    <item>
      <title>How To Set Up Multiple Datasources in Spring Boot 3</title>
      <dc:creator>Antonello Zanini</dc:creator>
      <pubDate>Thu, 24 Apr 2025 07:15:40 +0000</pubDate>
      <link>https://dev.to/antozanini/how-to-set-up-multiple-datasources-in-spring-boot-3-4089</link>
      <guid>https://dev.to/antozanini/how-to-set-up-multiple-datasources-in-spring-boot-3-4089</guid>
      <description>&lt;p&gt;Separating data into different databases is a typical approach in the microservices world. This means that each backend usually needs to access only its limited database. At the same time, a backend application may need to use more than one database. This is why you can set up multiple datasources in Spring Boot.   &lt;/p&gt;

&lt;p&gt;In this step-by-step tutorial, you will learn how to configure Spring Boot Data 3 to work with many databases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Suppose your Spring Boot RESTful backend needs to connect to the following two databases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;account&lt;/code&gt;: A MySQL database containing user data&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;company&lt;/code&gt;: A PostgreSQL database storing company data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The databases rely on two different RDBMS technologies and might even be on two different servers. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fphwt17v1p4k77iou4j7a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fphwt17v1p4k77iou4j7a.png" alt="The account and company databases" width="466" height="742"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the sake of simplicity, suppose that both databases has a single table. &lt;code&gt;account&lt;/code&gt; has the &lt;code&gt;user&lt;/code&gt; table and &lt;code&gt;company&lt;/code&gt; has the &lt;code&gt;product&lt;/code&gt; table. The goal here is to create a Spring Boot app that can use both databases. Let’s learn how to do it!&lt;/p&gt;

&lt;p&gt;Keep in mind that this is just a sample scenario and the proposed solution can easily be extended to an indefinite number of databases, regardless of their DBMS technology. &lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Multiple Datasources in JPA 3
&lt;/h2&gt;

&lt;p&gt;Follow this step-by-step tutorial and learn how to configure a Spring Boot 3 project to work with different databases in JPA.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before getting started, you need to add the following dependencies to your Spring Boot 3 project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa" rel="noopener noreferrer"&gt;Spring Boot starter data JPA 3+&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mvnrepository.com/artifact/org.postgresql/postgresql" rel="noopener noreferrer"&gt;PostgreSQL JDBC driver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mvnrepository.com/artifact/com.mysql/mysql-connector-j" rel="noopener noreferrer"&gt;MySQL Connector/J&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are a Maven user, add the lines below to your &lt;code&gt;pom.xml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.boot&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-boot-starter-data-jpa&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.mysql&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;mysql-connector-j&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.postgresql&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;postgresql&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise, if you are Gradle user, verify that &lt;code&gt;build.gradle&lt;/code&gt; contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.springframework.boot:spring-boot-starter-data-jpa'&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'com.mysql:mysql-connector-j'&lt;/span&gt;
&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'org.postgresql:postgresql'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last two dependencies are the PostgreSQL and MySQL drivers, respectively. These allow you to connect to MySQL and PostgreSQL servers. Make sure to add the right drivers to your project’s dependencies according to the database technologies your backend relies on. &lt;/p&gt;

&lt;p&gt;You now have everything you need to set up multiple datasources in Spring Boot. Time to learn how to do it!&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Mapping Your Entities
&lt;/h3&gt;

&lt;p&gt;Create an &lt;code&gt;entities&lt;/code&gt; package and split it into the following two subpackages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;account&lt;/code&gt;: Will contain all MySQL database entities&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;company&lt;/code&gt;: Will contain all PostgreSQL database entities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Define the &lt;code&gt;User&lt;/code&gt; and &lt;code&gt;Product&lt;/code&gt; JPA entities and place them in the right subpackage.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Defining the Datasource Connection Properties
&lt;/h3&gt;

&lt;p&gt;Add the connection info for the two target datasources to your &lt;code&gt;application.properties&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spring.datasource.account.url=jdbc:mysql://&amp;lt;MYSQL_DB_URL&amp;gt;/account
spring.datasource.account.username=&amp;lt;MYSQL_USERNAME&amp;gt;
spring.datasource.account.password=&amp;lt;MYSQL_PASSWORD&amp;gt;
spring.datasource.account.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.company.url=jdbc:postgresql://&amp;lt;POSTGRESQL_DB_CONNECTION_URL&amp;gt;/company
spring.datasource.company.username=&amp;lt;POSTGRESQL_USERNAME&amp;gt;
spring.datasource.company.password=&amp;lt;POSTGRESQL_PASSWORD&amp;gt;
spring.datasource.company.driver-class-name=org.postgresql.Driver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace all &lt;code&gt;&amp;lt;___&amp;gt;&lt;/code&gt; parameters with the right strings. Note that &lt;code&gt;account&lt;/code&gt; and &lt;code&gt;company&lt;/code&gt; after &lt;code&gt;spring.datasource&lt;/code&gt; are two custom options with the same name as the database they refer to. &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Defining the Datasource Configuration Files
&lt;/h3&gt;

&lt;p&gt;The info defined above can be used to create a database connection through a custom Spring Boot configuration file. &lt;/p&gt;

&lt;p&gt;This is what the &lt;a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html" rel="noopener noreferrer"&gt;&lt;code&gt;@Configuration&lt;/code&gt;&lt;/a&gt; file for the &lt;code&gt;account&lt;/code&gt; database looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.dbvis.demo.configs&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.dbvis.demo.entities.account.User&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.zaxxer.hikari.HikariDataSource&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.beans.factory.annotation.Qualifier&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.boot.autoconfigure.jdbc.DataSourceProperties&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.boot.context.properties.ConfigurationProperties&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Bean&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Primary&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.data.jpa.repository.config.EnableJpaRepositories&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.orm.jpa.JpaTransactionManager&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.transaction.PlatformTransactionManager&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.transaction.annotation.EnableTransactionManagement&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;javax.sql.DataSource&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Objects&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@EnableJpaRepositories&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;basePackages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.dbvis.demo.repositories.account"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;entityManagerFactoryRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"accountEntityManagerFactory"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;transactionManagerRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"accountTransactionManager"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@EnableTransactionManagement&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountDataSourceConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     *  Initialize a DataSourceProperties bean by reading the
     *  datasource info from "spring.datasource.account.*" configs
     *  in application.properties
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@Primary&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nd"&gt;@ConfigurationProperties&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spring.datasource.account"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;DataSourceProperties&lt;/span&gt; &lt;span class="nf"&gt;accountDataSourceProperties&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DataSourceProperties&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Create the datasource for the "account" database
     * using accountDataSourceProperties
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@Primary&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nd"&gt;@ConfigurationProperties&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spring.datasource.account.configuration"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;DataSource&lt;/span&gt; &lt;span class="nf"&gt;accountDataSource&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;accountDataSourceProperties&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initializeDataSourceBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HikariDataSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Configure Hibernate's EntityManager object
     * to look for Entity classes inside the "com.dbvis.entities.account" package
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@Primary&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"accountEntityManagerFactory"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;LocalContainerEntityManagerFactoryBean&lt;/span&gt; &lt;span class="nf"&gt;accountEntityManagerFactory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;EntityManagerFactoryBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;DataSource&lt;/span&gt; &lt;span class="n"&gt;dataSource&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accountDataSource&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Create a transaction manager for the EntityManager object created with
     * the "accountEntityManagerFactory" bean
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@Primary&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;PlatformTransactionManager&lt;/span&gt; &lt;span class="nf"&gt;accountTransactionManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nd"&gt;@Qualifier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"accountEntityManagerFactory"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="nc"&gt;LocalContainerEntityManagerFactoryBean&lt;/span&gt; &lt;span class="n"&gt;accountEntityManagerFactory&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JpaTransactionManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requireNonNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;accountEntityManagerFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getObject&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file reads the &lt;code&gt;account&lt;/code&gt; connection info from &lt;code&gt;application.properties&lt;/code&gt; to instantiate a &lt;a href="https://docs.oracle.com/en/java/javase/20/docs/api/java.sql/javax/sql/DataSource.html" rel="noopener noreferrer"&gt;&lt;code&gt;DataSource&lt;/code&gt;&lt;/a&gt; bean. Then, it uses to create a valid &lt;a href="https://jakarta.ee/specifications/platform/9/apidocs/jakarta/persistence/entitymanager" rel="noopener noreferrer"&gt;&lt;code&gt;EntityManager&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/TransactionManager.html" rel="noopener noreferrer"&gt;&lt;code&gt;TransactionManager&lt;/code&gt;&lt;/a&gt;. These two objects are what you need to handle a connection to a database. Check out the &lt;a href="https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; to learn more about how data access works in Spring Boot. &lt;/p&gt;

&lt;p&gt;Note the use of the &lt;code&gt;@Bean&lt;/code&gt; &lt;code&gt;name&lt;/code&gt; property and &lt;code&gt;@Qualifier&lt;/code&gt; annotation to &lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.spring-beans-and-dependency-injection" rel="noopener noreferrer"&gt;tell Spring Boot what specific bean to use&lt;/a&gt;. Also, notice that the scope of the &lt;code&gt;EntityManager&lt;/code&gt; gets restricted to the package containing the &lt;code&gt;User&lt;/code&gt; entity, which is the &lt;code&gt;account&lt;/code&gt; subpackage.&lt;/p&gt;

&lt;p&gt;Similarly, you need to define a &lt;code&gt;@Configuration&lt;/code&gt; file for &lt;code&gt;company&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.dbvis.demo.configs&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;


&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.dbvis.demo.entities.company.Product&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.zaxxer.hikari.HikariDataSource&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.beans.factory.annotation.Qualifier&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.boot.autoconfigure.jdbc.DataSourceProperties&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.boot.context.properties.ConfigurationProperties&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Bean&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.context.annotation.Configuration&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.data.jpa.repository.config.EnableJpaRepositories&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.orm.jpa.JpaTransactionManager&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.transaction.PlatformTransactionManager&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;javax.sql.DataSource&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Objects&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@EnableJpaRepositories&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;basePackages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.dbvis.demo.repositories.company"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;entityManagerFactoryRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"companyEntityManagerFactory"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;transactionManagerRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"companyTransactionManager"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CompanyDataSourceConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     *  Initialize a DataSourceProperties bean by reading the
     *  datasource info from "spring.datasource.company.*" configs
     *  in application.properties
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nd"&gt;@ConfigurationProperties&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spring.datasource.company"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;DataSourceProperties&lt;/span&gt; &lt;span class="nf"&gt;companyDataSourceProperties&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DataSourceProperties&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Create the datasource for the "company" database
     * using companyDataSourceProperties
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nd"&gt;@ConfigurationProperties&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"spring.datasource.company.configuration"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;DataSource&lt;/span&gt; &lt;span class="nf"&gt;companyDataSource&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;companyDataSourceProperties&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initializeDataSourceBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HikariDataSource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Configure Hibernate's EntityManager object
     * to look for Entity classes inside the "com.dbvis.entities.company" package
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"companyEntityManagerFactory"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;LocalContainerEntityManagerFactoryBean&lt;/span&gt; &lt;span class="nf"&gt;companyEntityManagerFactory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;EntityManagerFactoryBuilder&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dataSource&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;companyDataSource&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Create a transaction manager for the EntityManager object created with
     * the "companyEntityManagerFactory" bean
     */&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;PlatformTransactionManager&lt;/span&gt; &lt;span class="nf"&gt;companyTransactionManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nd"&gt;@Qualifier&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"companyEntityManagerFactory"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="nc"&gt;LocalContainerEntityManagerFactoryBean&lt;/span&gt; &lt;span class="n"&gt;companyEntityManagerFactory&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JpaTransactionManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requireNonNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;companyEntityManagerFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getObject&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main difference is that this config file refers to the &lt;code&gt;company&lt;/code&gt; values in &lt;code&gt;application.properties&lt;/code&gt;. Also, its Spring Boot beans are not marked with &lt;code&gt;@Primary&lt;/code&gt;. This is because only one &lt;code&gt;DataSource&lt;/code&gt; connection must be specified as primary. &lt;/p&gt;

&lt;p&gt;If you forget to mark one of the two connections as primary, Spring Boot will fail with the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parameter 0 of method companyEntityManagerFactory in com.dbvis.demo.configs.AAProductDataSourceConfig required a bean of type 'org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder' that could not be found.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Creating the JPA Repositories
&lt;/h3&gt;

&lt;p&gt;It only remains to define some &lt;a href="https://docs.spring.io/spring-data/jpa/docs/3.1.x/reference/html/#repositories" rel="noopener noreferrer"&gt;Spring Data repositories&lt;/a&gt;. This will help you perform CRUD operations on your tables. &lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;repositories&lt;/code&gt; packages. Then, consider splitting the package into as many subpackage as the number of databases you need to connect to. &lt;/p&gt;

&lt;p&gt;You can define a &lt;code&gt;UserRepository&lt;/code&gt; as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.dbvis.demo.repositories.account&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.dbvis.demo.entities.account.User&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.data.jpa.repository.JpaRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;UserRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JpaRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the &lt;code&gt;JpaRepository&lt;/code&gt; does not require any extra configuration. This is because the &lt;code&gt;AccountDataSourceConfig&lt;/code&gt; define earlier takes care of setting up everything required for the repository to work.&lt;/p&gt;

&lt;p&gt;Similarly, define &lt;code&gt;ProductRepository&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.dbvis.demo.repositories.company&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.dbvis.demo.entities.company.Product&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.data.jpa.repository.JpaRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ProductRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JpaRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now use the repository to perform operations on the &lt;code&gt;account&lt;/code&gt; and &lt;code&gt;company&lt;/code&gt; databases.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Putting It All Together
&lt;/h3&gt;

&lt;p&gt;This is the file structure your project should now have:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F743dygr5uc10rmhdaia0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F743dygr5uc10rmhdaia0.png" alt="The file structure of your Spring Boot project" width="446" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ProductController&lt;/code&gt; uses the &lt;code&gt;ProductRepository&lt;/code&gt; autowired object to retrieve data from the &lt;code&gt;company&lt;/code&gt; database, while the &lt;code&gt;UserController&lt;/code&gt; relies on a &lt;code&gt;UserRepository&lt;/code&gt; instance.&lt;/p&gt;

&lt;p&gt;Et voilà! Your Spring Boot 3 backend can now retrieve or write data to both the &lt;code&gt;account&lt;/code&gt; and &lt;code&gt;company&lt;/code&gt; databases, respectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring Multiple Datasources in a Single Tool With DbVisualizer
&lt;/h2&gt;

&lt;p&gt;If your project grows in complexity and requires multiple database connections, you will need an advanced tool to manage many datasources. This is what &lt;a href="https://www.dbvis.com/" rel="noopener noreferrer"&gt;DbVisualizer&lt;/a&gt; is all about!&lt;/p&gt;

&lt;p&gt;DbVisualizer is a complete database client that supports more than &lt;a href="https://www.dbvis.com/supported-databases/" rel="noopener noreferrer"&gt;50 different database technologies&lt;/a&gt;. This powerful tool enables you to take your database management to the next level. In particular, it comes with advanced data exploration capabilities, in-record data editing support, query optimization functionality, JSON and BLOB data type built-in support, and much more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzayq0cxl6mfjxl2qybu0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzayq0cxl6mfjxl2qybu0.png" alt="DbVisualizer" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What are you waiting for? &lt;a href="https://www.dbvis.com/download/" rel="noopener noreferrer"&gt;Try DbVisualizer for free today&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this tutorial, you learn how to configure Spring Boot 3 to work with several databases. In detail, you saw how to use Spring Boot Data 3 to connect to multiple datasources. As shown here, it is not difficult and only requires some special configuration.&lt;/p&gt;

&lt;p&gt;Knowing how to connect to multiple databases in Spring Boot is useful, but without the right data exploration tool everything becomes more complex. DbVisualizer is a MySQL, PostgreSQL, Oracle, and Microsoft SQL Server client that support more than other 50 database technologies. Thanks to its advanced features, managing numerous database connections simultaneously has never been easier. &lt;a href="https://www.dbvis.com/download/" rel="noopener noreferrer"&gt;Download DbVisualizer for free&lt;/a&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;Let’s answer some popular questions on multiple database connections with Spring Boot. &lt;/p&gt;

&lt;h3&gt;
  
  
  How many databases can you connect to in Spring Boot?
&lt;/h3&gt;

&lt;p&gt;You can connect to as many databases as the backend server can handle. There is no real limit to the number of database connections you can create with Spring Boot. At the same time, keep in mind that each connection to a data source takes resources. So, you should try to limit them to the bare minimum possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can the same Spring Boot app use different database technologies?
&lt;/h3&gt;

&lt;p&gt;Yes, Spring Boot Data supports the connections to several DBMS technologies, including both SQL and NoSQL technologies. You can connect to any database, as long as there is a supported Java JDBC driver. &lt;/p&gt;

&lt;h3&gt;
  
  
  How many database technologies can you use in a Spring Boot app?
&lt;/h3&gt;

&lt;p&gt;Spring Boot Data does not have limits on the number of database technologies you can connect to. You can have a single DBMS, several connections to it, or a heterogeneous scenario with connections to different database technologies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can you simultaneously connect to both SQL and NoSQL databases with Spring Boot Data?
&lt;/h3&gt;

&lt;p&gt;Yes, Spring Boot Data supports both SQL and NoSQL connections. Thus, you can set up both a MySQL and a MongoDB connection. For example, you can configure the Spring Boot application to work with SQL and MongoDB repositories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can two datasources have the same name in a Spring Boot app?
&lt;/h3&gt;

&lt;p&gt;Yes, two datasources can have the same name. As long as they are on two different database servers and are configured properly at the application layer, Spring Boot can work with two or more datasources with the same name. &lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>sql</category>
      <category>mysql</category>
    </item>
    <item>
      <title>Best Testing Practices in Node.js</title>
      <dc:creator>Antonello Zanini</dc:creator>
      <pubDate>Wed, 30 Oct 2024 12:54:41 +0000</pubDate>
      <link>https://dev.to/appsignal/best-testing-practices-in-nodejs-nbk</link>
      <guid>https://dev.to/appsignal/best-testing-practices-in-nodejs-nbk</guid>
      <description>&lt;p&gt;Testing is a critical aspect of software development, as it ensures your application works as intended and meets quality standards.&lt;br&gt;
In Node.js, testing is essential for the early detection of bugs in public endpoints.&lt;/p&gt;

&lt;p&gt;However, there are many challenges associated with testing in Node.&lt;br&gt;
External dependencies, asynchronous operations, and several possible input scenarios make writing tests a daunting task. Additionally, determining which components and aspects of your application to prioritize for testing can be challenging.&lt;/p&gt;

&lt;p&gt;With some guidelines and best practices, you can tackle testing challenges and ensure that your backend is robust and reliable.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll run through 15 top testing practices to write efficient, effective, and easy-to-maintain tests in Node.&lt;/p&gt;

&lt;p&gt;Time to become a master of Node testing!&lt;/p&gt;
&lt;h2&gt;
  
  
  The Golden Rule of Testing in Node.js
&lt;/h2&gt;

&lt;p&gt;Before diving into Node.js testing best practices, remember this &lt;a href="https://github.com/goldbergyoni/javascript-testing-best-practices?tab=readme-ov-file#section-0%EF%B8%8F%E2%83%A3-the-golden-rule" rel="noopener noreferrer"&gt;golden rule&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Testing code is not production code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Keep testing simple and avoid over-engineering. Tests should be intuitive and validate functionality, rather than implement complex design patterns or architectures.&lt;/p&gt;
&lt;h2&gt;
  
  
  Top 15 Node.js Testing Best Practices
&lt;/h2&gt;

&lt;p&gt;Now that you've learned the golden rule of testing in Node.js, we'll explore 15 top testing best practices. Keep in mind that these tips apply to any Node test runner or testing library.&lt;/p&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Consider the Test Pyramid
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://martinfowler.com/articles/practical-test-pyramid.html" rel="noopener noreferrer"&gt;Test Pyramid&lt;/a&gt; is a concept devised by Mike Cohn that first appeared in his book &lt;em&gt;Succeeding with Agile&lt;/em&gt;, published in 2009.&lt;br&gt;
This pyramid is a visual metaphor that explains how to approach testing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa078264axq2akwwm5y4p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa078264axq2akwwm5y4p.png" alt="Practical Test Pyramid" width="466" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source: &lt;a href="https://martinfowler.com/articles/practical-test-pyramid.html" rel="noopener noreferrer"&gt;The Practical Test Pyramid&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Test Pyramid contains three layers. These represent what a test suite should consist of and the amount of testing to be performed on each level. The pyramid also highlights the expected performance and level of isolation required by each layer.&lt;/p&gt;

&lt;p&gt;From a modern perspective, though, the test pyramid may appear overly simplistic. Even so, there are two lessons we can learn from it when designing Node.js tests:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Write tests with different levels of granularity.&lt;/li&gt;
&lt;li&gt;The more high-level you get, the fewer tests you should have.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  2. Define Easy-to-Understand Test Titles
&lt;/h3&gt;

&lt;p&gt;Engineers or DevOps experts who take care of deployments may not be familiar with code. When a deployment fails because of a failed test, their first reaction will be to look at the logs or test reports. In this case, the only thing they might see is the name of the failed test.&lt;/p&gt;

&lt;p&gt;That's why each test should have a title that is informative enough to explain exactly what the test does. Specifically, a test name should answer these three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What is being tested?&lt;/li&gt;
&lt;li&gt;What is the expected result?&lt;/li&gt;
&lt;li&gt;Under what circumstances? / In what scenario?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a reference, take a look at the Node.js test below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// unit/component under test (1.)&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user authentication service&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;login functionality&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// expected result (2.) and circumstance/scenario (3.)&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should reject login with incorrect password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Include Tags with Your Test Titles
&lt;/h3&gt;

&lt;p&gt;Adding tags to test titles improves test organization. Tags such as "#api" or "#authentication" help categorize tests and allow you to run similar tests in batches, because Node test runners can usually execute tests that contain a specific string in their titles. Also, by using tags, it's easier to find tests using the search capabilities of your IDE.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Follow the Arrange, Act, Assert (AAA) Pattern in Your Tests
&lt;/h3&gt;

&lt;p&gt;The Arrange, Act, Assert pattern ensures that each test has a clear scope and is well-structured. A test written according to this pattern should be based on these three phases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Arrange&lt;/strong&gt;: Set up the test environment. This also includes adding records to the test database and defining any necessary stubs and/or mocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Act&lt;/strong&gt;: Execute the code that will be tested. That generally boils down to calling a single function or method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assert&lt;/strong&gt;: Verifies the expected outcomes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach to test writing enhances readability and maintainability.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Focus on Testing Public Behavior
&lt;/h3&gt;

&lt;p&gt;A well-known and effective strategy for API testing is to focus on public behavior. At the end of the day, the main goal of your testing operation is to make sure that your Node endpoints produce the expected results when given specific inputs. This contract-based approach to testing, also known as &lt;a href="https://en.wikipedia.org/wiki/Black-box_testing" rel="noopener noreferrer"&gt;black-box testing&lt;/a&gt;, aims to test the expected input-output relationships without delving into internal implementation details.&lt;/p&gt;

&lt;p&gt;Libraries like &lt;a href="https://github.com/eugef/node-mocks-http" rel="noopener noreferrer"&gt;&lt;code&gt;node-mocks-http&lt;/code&gt;&lt;/a&gt; enable the creation of &lt;code&gt;req&lt;/code&gt; and &lt;code&gt;res&lt;/code&gt; objects to call routing functions and simulate incoming requests.&lt;br&gt;
You can then inspect the resulting &lt;code&gt;res&lt;/code&gt; object to check API behavior, such as via &lt;a href="https://json-schema.org/" rel="noopener noreferrer"&gt;JSON schema validation&lt;/a&gt;. Additionally, you can assert whether the HTTP status or headers set on the &lt;code&gt;res&lt;/code&gt; object align with expectations.&lt;/p&gt;

&lt;p&gt;By targeting public behavior, you can verify that your APIs adhere to defined contracts with just a few tests. This approach checks that your Node.js endpoints behave as intended for end users, without you having to test dozens of functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Use a Dedicated Database in Each Test
&lt;/h3&gt;

&lt;p&gt;Using dedicated databases in every test is key to ensuring isolation and reproducibility. The biggest objection you might have is that this approach leads to data duplication. Well, don't forget the golden rule!&lt;/p&gt;

&lt;p&gt;Employing shared databases for multiple tests can be the source of flaky behavior. A test might fail because the record it's supposed to operate on has been modified or deleted by another test running at the same time.&lt;/p&gt;

&lt;p&gt;Setting up separate databases for each test prevents data contamination and interference between tests. This leads to robust test results and opens the door to safe test parallel execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Define an Effective Data Clean Strategy
&lt;/h3&gt;

&lt;p&gt;The AAA pattern and previous best practices recommend that each test sets up its own database. To prevent filling your memory or disk with unnecessary data, you must define an effective data-cleaning strategy.&lt;/p&gt;

&lt;p&gt;There are a few possible options for data cleaning, including cleaning the database:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After every single test.&lt;/li&gt;
&lt;li&gt;After all tests have finished.&lt;/li&gt;
&lt;li&gt;Periodically, at set intervals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The right strategy depends on your specific project requirements and architecture. Regardless of the approach selected, don't clean the database on test failures. Having access to the data associated with a failed test is essential for debugging and understanding what went wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Use Realistic Input Data
&lt;/h3&gt;

&lt;p&gt;When testing methods and functions that need input data, you might be tempted to use random strings and values like "foo" or "1234."&lt;br&gt;
While this reduces the mental effort of coming up with accurate input, it's a bad practice. Instead, always test functions with realistic inputs. This way, you can ensure that your Node.js tests are more representative of real-world scenarios.&lt;/p&gt;

&lt;p&gt;For automatic data generation, consider using libraries like &lt;a href="https://chancejs.com/" rel="noopener noreferrer"&gt;Chance&lt;/a&gt; or &lt;a href="https://fakerjs.dev/" rel="noopener noreferrer"&gt;Faker&lt;/a&gt;.&lt;br&gt;
These can generate fake — yet realistic — data resembling production data, such as real-world phone numbers, usernames, credit card numbers, company names, and text.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Use Property-Based Testing to Cover All Possible Input Combinations
&lt;/h3&gt;

&lt;p&gt;While you can test a function with realistic data, testing all possible user input combinations may take too much time, especially when accepting many inputs. The problem is that one of those untested combinations might lead to an unexpected result. That's where property-based testing comes in!&lt;/p&gt;

&lt;p&gt;This technique involves automatically testing a function with a wide range of input combinations, increasing the chances of discovering bugs. Instead of manually selecting a few input samples, property-based testing generates numerous permutations to thoroughly examine how your code handles different scenarios. This helps identify edge cases and unexpected input interactions, detecting issues that might not emerge with manual testing.&lt;/p&gt;

&lt;p&gt;For instance, the function &lt;code&gt;addNewProduct(id, name, isDiscount)&lt;/code&gt; can be tested with multiple combinations of &lt;code&gt;(number, string, boolean)&lt;/code&gt; such as &lt;code&gt;(1, "iPear", false)&lt;/code&gt;, &lt;code&gt;(2, "Universe XL", true)&lt;/code&gt;, or &lt;code&gt;(0, undefined, null)&lt;/code&gt;. By using libraries like &lt;a href="https://fast-check.dev/" rel="noopener noreferrer"&gt;&lt;code&gt;fast-check&lt;/code&gt;&lt;/a&gt;, you can automate property-based testing within your favorite test runner.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Prefer Stubs and Spies Over Mocks
&lt;/h3&gt;

&lt;p&gt;When using &lt;a href="https://martinfowler.com/bliki/TestDouble.html" rel="noopener noreferrer"&gt;test doubles&lt;/a&gt;, prefer stubs and spies over mocks to maintain simplicity and clarity. Stubs replace functions and control their behavior, while spies track function calls and arguments. Both are less intrusive and easier to manage compared to mocks, which often involve complex logic to interact with the other components in your application.&lt;/p&gt;

&lt;p&gt;Libraries like &lt;a href="https://sinonjs.org/" rel="noopener noreferrer"&gt;Sinon.JS&lt;/a&gt; provide robust support for stubs and spies, enabling precise control and inspection of your code's interactions. By favoring stubs and spies, you can keep your tests concise, easier to understand, and less prone to errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  11. Avoid Catching Expected Errors
&lt;/h3&gt;

&lt;p&gt;Some tests verify whether a function throws a specific error on faulty input. A common mistake when writing such tests is surrounding the function call with a &lt;code&gt;try ... catch&lt;/code&gt; statement and asserting whether the test entered the &lt;code&gt;catch&lt;/code&gt; branch.&lt;/p&gt;

&lt;p&gt;Avoid that, as it only makes the test logic verbose and more difficult to read and maintain. Instead, prefer dedicated assertions like &lt;a href="https://www.chaijs.com/api/bdd/#method_throw" rel="noopener noreferrer"&gt;&lt;code&gt;expect(&amp;lt;function call&amp;gt;).to.throw(&amp;lt;blank or error type&amp;gt;)&lt;/code&gt; in Chai&lt;/a&gt; or &lt;a href="https://jestjs.io/docs/expect#tothrowerror" rel="noopener noreferrer"&gt;&lt;code&gt;expect(&amp;lt;function call&amp;gt;).toThrow(&amp;lt;blank or error type&amp;gt;)&lt;/code&gt; in Jest&lt;/a&gt;. These assertions provide a clearer and more elegant way to test for thrown errors without catching them.&lt;/p&gt;

&lt;h3&gt;
  
  
  12. Don't Forget to Test Middleware Functions
&lt;/h3&gt;

&lt;p&gt;As mentioned in testing tip 5 above, you should focus on testing public behavior. However, you shouldn't ignore internal code entirely. For instance, dozens of endpoints exposed by your backend may rely on the same middleware. A bug in that middleware would introduce faulty behavior across many APIs!&lt;/p&gt;

&lt;p&gt;While tests for endpoints will fail because of that bug, understanding that the cause of all those failures is down to the same middleware may not be easy. Given their vital importance in a Node.js architecture, it makes sense to have tests dedicated to middleware functions.&lt;/p&gt;

&lt;p&gt;A middleware can be tested like any other function. Forge &lt;code&gt;req&lt;/code&gt; and &lt;code&gt;res&lt;/code&gt; objects with libraries like &lt;code&gt;node-mocks-http&lt;/code&gt; and verify that it performs the required actions on these objects. This way, you can guarantee that your middleware layer is reliable and doesn't introduce errors to your endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  13. Produce Useful Test Coverage Reports
&lt;/h3&gt;

&lt;p&gt;Most Node.js testing technologies come with reporting capabilities.&lt;br&gt;
This means they can be configured to generate reports in HTML, XML, or other formats after a test suite run. These reports highlight test results and provide useful statistics.&lt;/p&gt;

&lt;p&gt;The reports prove invaluable for QA engineers, who may not be familiar with a codebase, by identifying failing tests and areas of an application that require further tests. A key metric in these reports is &lt;a href="https://en.wikipedia.org/wiki/Code_coverage" rel="noopener noreferrer"&gt;code or test coverage&lt;/a&gt;, which summarizes the percentage of your codebase exercised during testing.&lt;/p&gt;

&lt;p&gt;Coverage reports provide insights into the quality and completeness of your tests, highlighting areas and components that require additional attention. By analyzing and monitoring the results of these reports, you can identify untested code paths and monitor the evolution of your testing operation. You should always configure your testing library to produce test coverage reports.&lt;/p&gt;

&lt;h3&gt;
  
  
  14. Run Performance and Stress Tests
&lt;/h3&gt;

&lt;p&gt;Writing tests in a Node.js application isn't always about test coverage. You also need to guarantee that your backend remains responsive and fast, even under stressful conditions. You can achieve that by running performance and stress tests. That ensures that your application can handle an unexpected workload and maintain optimal performance under various traffic conditions.&lt;/p&gt;

&lt;p&gt;Performance tests evaluate response time, throughput, and resource usage to help you identify bottlenecks. Stress tests simulate heavy loads and peak traffic scenarios to assess system stability and resilience. By regularly performing these types of tests, you can proactively identify and address performance issues and prevent downtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  15. Refer to Other Popular Best Practices
&lt;/h3&gt;

&lt;p&gt;Staying up-to-date on popular best practices is critical.&lt;br&gt;
Refer regularly to resources such as the Node.js docs, trusted blogs, and community forums to make sure that your tests remain robust, secure, and efficient. Don't forget that this, or any other, guide is only an introduction to the complex world of Node.js testing best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up: Improve Your Node.js Testing Process
&lt;/h2&gt;

&lt;p&gt;In this blog post, we took a look at the golden rule of Node.js testing and explored some of the most relevant best testing practices.&lt;/p&gt;

&lt;p&gt;You now know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To consider testing code not as production code&lt;/li&gt;
&lt;li&gt;What components in your Node.js application you should focus your testing efforts on&lt;/li&gt;
&lt;li&gt;Many tips and tricks to keep your tests simple, effective, and easy to maintain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you liked this post, &lt;a href="https://blog.appsignal.com/javascript-sorcery" rel="noopener noreferrer"&gt;subscribe to our JavaScript Sorcery list&lt;/a&gt; for a monthly deep dive into more magical JavaScript tips and tricks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.P.S. If you need an APM for your Node.js app, go and &lt;a href="https://www.appsignal.com/nodejs" rel="noopener noreferrer"&gt;check out the AppSignal APM for Node.js&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>node</category>
    </item>
    <item>
      <title>DbVisualizer 24.2: A Complete Review</title>
      <dc:creator>Antonello Zanini</dc:creator>
      <pubDate>Mon, 23 Sep 2024 14:01:52 +0000</pubDate>
      <link>https://dev.to/antozanini/dbvisualizer-242-a-complete-review-3d18</link>
      <guid>https://dev.to/antozanini/dbvisualizer-242-a-complete-review-3d18</guid>
      <description>&lt;p&gt;&lt;em&gt;Discover what DbVisualizer 24.2 has to offer in this in-depth review and learn why it represents the biggest DbVisualizer release to date&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;DbVisualizer 24.2 marks a major step for the database client with the highest user satisfaction rate on the market, taking it to the next level thanks to a refined user interface and many other improvements.&lt;/p&gt;

&lt;p&gt;In this review, you’ll discover why DbVisualizer underwent a UI redesign, what new features and improvements version 24.2 has introduced, and how to try this latest release.&lt;/p&gt;

&lt;p&gt;Let’s find out what an experienced developer and database administrator thinks of DbVisualizer 24.2 in my review!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Need for a Restyling of DbVisualizer
&lt;/h2&gt;

&lt;p&gt;As you may already know, &lt;a href="https://www.dbvis.com/" rel="noopener noreferrer"&gt;DbVisualizer&lt;/a&gt; has been the database client of choice for years according to top companies, government agencies, and individual developers. &lt;/p&gt;

&lt;p&gt;According to the official site, over the years, &lt;strong&gt;more than 6 million users have downloaded the software, and more than 28,000 companies spanning 145 countries&lt;/strong&gt; are currently using the commercial &lt;a href="https://www.dbvis.com/pricing/" rel="noopener noreferrer"&gt;Pro version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The company proudly states that Tesla, NASA, Apple, Netflix, and many other leading companies have chosen DbVisualizer for their database operations. &lt;/p&gt;

&lt;p&gt;No surprise, over 216 users on G2 and more than 96 users on Capterra have given it top-notch positive reviews. With a rating of &lt;a href="https://www.g2.com/products/dbvisualizer/reviews" rel="noopener noreferrer"&gt;&lt;strong&gt;4.6/5 on G2&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://www.capterra.com/p/197030/DbVisualizer/" rel="noopener noreferrer"&gt;&lt;strong&gt;4.8/5 on Capterra&lt;/strong&gt;&lt;/a&gt;, DbVisualizer has the highest user satisfaction rate in the market. &lt;em&gt;Try DbVisualizer, and you’ll agree with them!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The secret of its success? Well, it must be the &lt;a href="https://www.dbvis.com/supported-databases/" rel="noopener noreferrer"&gt;support for more than 50 databases&lt;/a&gt; and a Java core, which makes the tool multi-platform and extremely reliable. &lt;/p&gt;

&lt;p&gt;However, in my opinion as an experienced developer, what sets DbVisualizer apart from other database clients is its extensive range of capabilities. &lt;strong&gt;The software offers a stunning number of utilities, options, and functionalities&lt;/strong&gt;, appealing to both beginners and professional database administrators with years of experience. That’s impressive!&lt;/p&gt;

&lt;p&gt;Actually, you can easily tell that the development team behind DbVisualizer has focused their efforts on providing robust, powerful, easy-to-use, and time-saving features. &lt;/p&gt;

&lt;p&gt;With DbVisualizer 24.2, &lt;strong&gt;the team appears to have put the same dedication into improving the UI and UX&lt;/strong&gt;, aiming to elevate the database client we all love to the next level.&lt;/p&gt;

&lt;p&gt;Time to find out more about this new version of DbVisualizer!&lt;/p&gt;

&lt;h2&gt;
  
  
  DbVisualizer 24.2: The Biggest Update So Far
&lt;/h2&gt;

&lt;p&gt;Released in June 2024, &lt;a href="https://www.dbvis.com/whatsnew/24.2/" rel="noopener noreferrer"&gt;DbVisualizer 24.2&lt;/a&gt; has been presented as the most significant update in the history of DbVisualizer (as of this writing, at least). &lt;/p&gt;

&lt;p&gt;This new version introduces numerous changes, with the most noticeable being the &lt;strong&gt;revamped user interface&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feca8tmtb1x9o34w394uf.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feca8tmtb1x9o34w394uf.gif" alt="The new UI against the old one" width="760" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, that’s definitely a remarkable improvement! &lt;/p&gt;

&lt;p&gt;The new UI now appears more intuitive, modern, and user-friendly. In addition to the impressive new interface and updated themes, the 24.2 release introduces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;7 new features&lt;/li&gt;
&lt;li&gt;17 improvements&lt;/li&gt;
&lt;li&gt;11 bug fixes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These additions include support for persistent connections via the &lt;a href="https://www.dbvis.com/feature/command-line-interface-dbviscmd/" rel="noopener noreferrer"&gt;&lt;code&gt;dbviscmd&lt;/code&gt;&lt;/a&gt; CLI tool, dedicated actions to select the previous/next statement in the SQL editor, support for Azure Synapse Analytics, enhanced autocomplete capabilities, full support for roles in PostgreSQL, automatic theme switching, support for Databricks, and many others. &lt;em&gt;Those are a lot of new features!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Discover all the changes provided by DbVisualizer 24.2 in the &lt;a href="https://www.dbvis.com/releasenotes/" rel="noopener noreferrer"&gt;release notes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s New in DbVisualizer 24.2?
&lt;/h2&gt;

&lt;p&gt;Easy question! Just have a look at the summary table below. You’ll discover the most important changes introduced in this major release of DbVisualizer:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Type&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Title&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;New Feature&lt;/td&gt;
&lt;td&gt;UX/UI&lt;/td&gt;
&lt;td&gt;Full window support on macOS. Automatic theme switching, new dark and light themes, new scalable icons&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New Feature&lt;/td&gt;
&lt;td&gt;Command Line Support (&lt;code&gt;dbviscmd&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Add support for creating persistent database connections using &lt;code&gt;dbviscmd&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New Feature&lt;/td&gt;
&lt;td&gt;DB Support: Azure Synapse&lt;/td&gt;
&lt;td&gt;Add support for Azure Synapse Analytics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New Feature&lt;/td&gt;
&lt;td&gt;DB Support: Databricks&lt;/td&gt;
&lt;td&gt;Add basic support for Databricks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New Feature&lt;/td&gt;
&lt;td&gt;DB Support: MariaDB/Oracle&lt;/td&gt;
&lt;td&gt;Navigate between procedures and functions in the package body editor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New Feature&lt;/td&gt;
&lt;td&gt;Export&lt;/td&gt;
&lt;td&gt;Add support for generating &lt;code&gt;MERGE&lt;/code&gt; statements when exporting data as SQL and for trimming text values when exporting data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New Feature&lt;/td&gt;
&lt;td&gt;SQL Editor&lt;/td&gt;
&lt;td&gt;Add actions to select the previous/next statement in the editor (&lt;code&gt;Ctrl+Alt+Up/Down&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Improvement&lt;/td&gt;
&lt;td&gt;Auto-Completion&lt;/td&gt;
&lt;td&gt;Automatically qualify columns when the column name alone is ambiguous&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Improvement&lt;/td&gt;
&lt;td&gt;Cell Viewer/Editor/Grid Component&lt;/td&gt;
&lt;td&gt;Add support for SVG images in the cell viewer and the data grid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Improvement&lt;/td&gt;
&lt;td&gt;Create/Alter Table/DB Support: PostgreSQL&lt;/td&gt;
&lt;td&gt;Add support for sequence names in auto-generated columns in PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Improvement&lt;/td&gt;
&lt;td&gt;DB Support: PostgreSQL&lt;/td&gt;
&lt;td&gt;Add support for roles in PostgreSQL (replacing users and groups)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Improvement&lt;/td&gt;
&lt;td&gt;DB Support: Redshift&lt;/td&gt;
&lt;td&gt;Add support for materialized views in Redshift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Improvement&lt;/td&gt;
&lt;td&gt;General&lt;/td&gt;
&lt;td&gt;Improved support for arrays&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Improvement&lt;/td&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;Add the option to reset the master password also when a master password is required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bug Fix&lt;/td&gt;
&lt;td&gt;Create/Alter Table/DB Support: PostgreSQL&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NOT NULL&lt;/code&gt; can be omitted in the &lt;a href="https://www.dbvis.com/thetable/sql-ddl-the-definitive-guide-on-data-definition-language/" rel="noopener noreferrer"&gt;DDL&lt;/a&gt; for auto-generated columns in PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bug Fix&lt;/td&gt;
&lt;td&gt;DB Support: ClickHouse&lt;/td&gt;
&lt;td&gt;Can't list databases in older versions of ClickHouse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bug Fix&lt;/td&gt;
&lt;td&gt;Export/Import User Settings&lt;/td&gt;
&lt;td&gt;Passwords are not included when exporting/importing SSH configurations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bug Fix&lt;/td&gt;
&lt;td&gt;Query Builder&lt;/td&gt;
&lt;td&gt;When loading a query containing an unsupported operator into the query builder, it’s replaced with "="&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let me now review each of the most interesting changes introduced by DbVisualizer 24.2!&lt;/p&gt;

&lt;h3&gt;
  
  
  A Fresh New UI
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvwkf57coskb7u1gldbg6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvwkf57coskb7u1gldbg6.png" alt="A complete overview of the new UI" width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In release 24.2, &lt;strong&gt;DbVisualizer’s UI has been significantly refined&lt;/strong&gt;. As the GIF below suggests, that’s not just a color adjustment but a complete UI overhaul. &lt;/p&gt;

&lt;p&gt;According to the release notes, the goal of the development was to create a balanced work environment for both light and dark theme users.&lt;/p&gt;

&lt;p&gt;Well, I think they achieved their goal brilliantly! &lt;em&gt;The new UI just looks fresh and intuitive.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The dark theme has become darker, and the light theme has become lighter, with DbVisualizer that can now automatically match the dark or light setting of your OS. &lt;/p&gt;

&lt;p&gt;You can tell that the team worked hard to balance the look and colors, as well as introduce small improvements to margins around elements and line heights in lists.&lt;/p&gt;

&lt;p&gt;Another improvement made by the team in DbVisualizer 24.2 is &lt;strong&gt;a new set of icons&lt;/strong&gt;. They replaced the old icons with modern and scalable SVG icons, ensuring they look good on any screen and resolution settings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1pkpx2bg09jrux7tygz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe1pkpx2bg09jrux7tygz.gif" alt="The new icons compared to the old ones" width="504" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To avoid disappointing or disorienting DbVisualizer veterans, the organization of UI elements remains largely unchanged. Only the placement of some buttons has been improved to enhance usability and make daily use more intuitive. (&lt;em&gt;Thank you for considering us, DbVisualizer team!&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;Check out the docs to see &lt;a href="https://www.dbvis.com/docs/24.2/getting-the-most-out-of-the-gui/" rel="noopener noreferrer"&gt;how to get the most out of the new UI&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improved Database Support
&lt;/h3&gt;

&lt;p&gt;Below are the database support improvements added in version 24.2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Azure Synapse Analytics&lt;/strong&gt;: DbVisualizer now has extended support for dedicated and serverless SQL pools in &lt;a href="https://azure.microsoft.com/en-us/products/synapse-analytics" rel="noopener noreferrer"&gt;Azure Synapse Analytics&lt;/a&gt;. That includes support for database-scoped credentials, external file formats and data sources, and external tables. For more information, see the &lt;a href="https://www.dbvis.com/database/azure-synapse-dedicated/" rel="noopener noreferrer"&gt;Azure Synapse Dedicated&lt;/a&gt; and &lt;a href="https://www.dbvis.com/database/azure-synapse-serverless/" rel="noopener noreferrer"&gt;Azure Synapse Serverless&lt;/a&gt; pages on the official site.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cassandra&lt;/strong&gt;: Added support for the &lt;a href="https://github.com/ing-bank/cassandra-jdbc-wrapper" rel="noopener noreferrer"&gt;JDBC wrapper of the Java driver for Cassandra&lt;/a&gt;, which is more powerful and future-proof than previous alternatives. Moreover, other Cassandra-related improvements have been implemented, such as support for overloaded functions and error marking in the &lt;em&gt;Procedure Editor&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Databricks&lt;/strong&gt;: Introduced basic database support for &lt;a href="https://www.databricks.com/" rel="noopener noreferrer"&gt;Databricks&lt;/a&gt;, with a pre-defined entry for Databricks in the driver manager for simplified setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Greenplum&lt;/strong&gt;: Database integration extended to &lt;a href="https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/relnotes-release-notes.html" rel="noopener noreferrer"&gt;Greenplum 7&lt;/a&gt;, including adjustments for partitioned tables, generated columns, constraints, procedures, functions, aggregates, and sequences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MariaDB&lt;/strong&gt;: New navigation panel added to the &lt;em&gt;Package Body Editor&lt;/em&gt;, making it easier to navigate between procedures and functions in a package.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MySQL&lt;/strong&gt;: Added the possibility to create and update &lt;a href="https://www.dbvis.com/thetable/the-ultimate-guide-to-generated-columns/" rel="noopener noreferrer"&gt;generated columns&lt;/a&gt; in the &lt;em&gt;Create Table&lt;/em&gt; and &lt;em&gt;Alter Table&lt;/em&gt; dialogs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Oracle&lt;/strong&gt;: The same improvement added for MariaDB has also been introduced for Oracle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt;: Support for PostgreSQL has been extended to include role management. Also, users can now specify the sequence name when defining auto-generated columns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redshift&lt;/strong&gt;: Added support for materialized views in Redshift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snowflake&lt;/strong&gt;: Introduced support for additional table types in Snowflake, including dynamic tables, event tables, hybrid tables, and iceberg tables.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Enhanced Data Export Options
&lt;/h3&gt;

&lt;p&gt;Among the tons of features supported by DbVisualizer is the ability to &lt;a href="https://www.dbvis.com/feature/export-schema-database/" rel="noopener noreferrer"&gt;&lt;strong&gt;export databases/schemas&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;a href="https://www.dbvis.com/feature/table-export/" rel="noopener noreferrer"&gt;&lt;strong&gt;tables&lt;/strong&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The first option is ideal for exporting DDL for objects such as tables, views, procedures, functions, triggers, packages, and package bodies in a database/schema. The second option is instead dedicated to exporting table data in various formats, including as an SQL file or to the system clipboard.&lt;/p&gt;

&lt;p&gt;With DbVisualizer 24.2, when exporting data in SQL format, users can now select between &lt;code&gt;INSERT&lt;/code&gt; statements and &lt;code&gt;MERGE&lt;/code&gt; statements:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5bremsyllyboftayllnp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5bremsyllyboftayllnp.png" alt="Note the ‘Generate MERGE statements’ option" width="800" height="568"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Compared to &lt;code&gt;INSERT&lt;/code&gt; statements, &lt;code&gt;MERGE&lt;/code&gt; statements allow for the merging of data from the source table to the target table. &lt;/p&gt;

&lt;p&gt;That ensures that existing rows in the target table are updated, while new rows are inserted, offering an efficient and robust way to synchronize data between tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Support for text functions&lt;/strong&gt; has also been added to the data export dialog. When selected, the text function will be applied to all text columns that are exported. Currently, the available options are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep the text as is&lt;/li&gt;
&lt;li&gt;Trim both ends&lt;/li&gt;
&lt;li&gt;Trim the right end&lt;/li&gt;
&lt;li&gt;Trim the left end&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another tweak introduced in the new version of DbVisualizer I found amazing is that, when exporting data to a file, &lt;strong&gt;the correct filename extension is now appended automatically depending on the selected output format&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;When the user selects a format, the export file name is updated accordingly with the appropriate extension. That saves you a lot of unwanted errors, trust me!&lt;/p&gt;

&lt;p&gt;As an additional improvement, when previewing the export in SQL format, syntax highlighting is also applied to the generated code to make it easier to read.&lt;/p&gt;

&lt;h3&gt;
  
  
  Refined SQL Editor
&lt;/h3&gt;

&lt;p&gt;As all DbVisualier users know, one of its most beloved features is its fully-featured &lt;a href="https://www.dbvis.com/features/sql-editor/" rel="noopener noreferrer"&gt;SQL editor&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In release 24.2, tthe editor has been further improved with several enhancements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Advanced substitution in&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;Find and Replace&lt;/em&gt;&lt;/strong&gt;: The &lt;a href="https://www.dbvis.com/docs/24.2/finding-database-objects-and-data/finding-and-replacing-text-in-the-editor/" rel="noopener noreferrer"&gt;&lt;em&gt;Find and Replace&lt;/em&gt;&lt;/a&gt; panel now includes buttons to add newlines and supports literals &lt;code&gt;\n&lt;/code&gt; and &lt;code&gt;\t&lt;/code&gt;. Also, it accepts &lt;code&gt;\L&lt;/code&gt; and &lt;code&gt;\U&lt;/code&gt; for lowercase and uppercase manipulation, respectively.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpeapkq4ke3v1xdzwhncl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpeapkq4ke3v1xdzwhncl.png" alt="Note the special characters in the *Find and Replace* section" width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Smarter auto-completion&lt;/strong&gt;: When auto-completing column names, these will now be automatically qualified if the column name alone is ambiguous. &lt;a href="https://www.dbvis.com/docs/24.2/working-with-sql/editing-sql-scripts/#auto-completion" rel="noopener noreferrer"&gt;See the details in the documentation&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Select next/previous statement&lt;/strong&gt;: Added support for the &lt;code&gt;CTRL+ALT+UP&lt;/code&gt; and &lt;code&gt;CTRL+ALT+DOWN&lt;/code&gt; shortcuts to select the next and previous statements in the current SQL script, respectively.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Support for a list of choices in custom variables&lt;/strong&gt;: Introduced the possibility of defining variables with a list of available values for advanced scripting.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other Changes Worth Mentioning
&lt;/h3&gt;

&lt;p&gt;While I would love to dig into each of the updates and improvements added by DbVisualizer 24.2, they’re just too many. Thus, here are some other noteworthy changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved support for arrays:&lt;/strong&gt; DbVisualizer now supports a JSON-based syntax for presenting and editing arrays. Additionally, it has extended support for understanding the content and structure of array objects. This enables you to validate the input before sending data to the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uncommitted SQL statements:&lt;/strong&gt; When warning about auto-commit being turned off, DbVisualizer now displays which statements will be committed as a result of the commit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Special characters in text fields:&lt;/strong&gt; Newlines and tabs in text fields are now rendered as symbols in the &lt;em&gt;Data&lt;/em&gt; grid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images in the&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;Data&lt;/em&gt;&lt;/strong&gt; &lt;strong&gt;grid:&lt;/strong&gt; Binary columns containing SVG images are now displayed in the &lt;em&gt;Data&lt;/em&gt; grid. The team has also implemented multi-threading rendering for faster image loading.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent database connections via CLI:&lt;/strong&gt; The DbVisualizer CLI now supports the creation of persistent database connections via the command line.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To learn how to make the most of all new features, check out &lt;a href="https://www.dbvis.com/docs/24.2/" rel="noopener noreferrer"&gt;DbVisualizer 24.2 User Guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is 24.2 the Best Version of DbVisualizer?
&lt;/h2&gt;

&lt;p&gt;Yes, I have no doubt that version 24.2 is the best release of DbVisualizer ever! &lt;/p&gt;

&lt;p&gt;That’s clear when you consider the pros and cons of this new version:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👍 Pros&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fresh, modern UI&lt;/li&gt;
&lt;li&gt;New SVG icons&lt;/li&gt;
&lt;li&gt;Over 10 bug fixes for a more reliable experience&lt;/li&gt;
&lt;li&gt;Support for Azure Synapse Analytics&lt;/li&gt;
&lt;li&gt;Improved SQL editor&lt;/li&gt;
&lt;li&gt;Enhanced autocomplete capabilities&lt;/li&gt;
&lt;li&gt;Improved support for arrays&lt;/li&gt;
&lt;li&gt;New data export options&lt;/li&gt;
&lt;li&gt;Persistent connections via &lt;code&gt;dbviscmd&lt;/code&gt; in the command line&lt;/li&gt;
&lt;li&gt;Basic support for Databricks&lt;/li&gt;
&lt;li&gt;Support for SVG images in the &lt;em&gt;Data&lt;/em&gt; view&lt;/li&gt;
&lt;li&gt;Improved &lt;em&gt;Find and Replace&lt;/em&gt; feature&lt;/li&gt;
&lt;li&gt;New shortcuts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👎 &lt;strong&gt;Cons&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adapting to the new UI might take some time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The list of advantages outweighs the drawbacks. The only notable disadvantage is the adaptation to the new UI, which might throw into confusion some veteran users (like me). Still, that’s the inevitable price to pay for a revamped and more modern user interface. &lt;/p&gt;

&lt;p&gt;At the end of the day, our habits as users change as technology evolves, and software UIs must evolve accordingly.&lt;/p&gt;

&lt;p&gt;The good news is that the team has introduced other &lt;strong&gt;tweaks and UX optimizations to greatly reduce the feeling of disorientation&lt;/strong&gt;. Surprisingly, the new UI just feels familiar. Give it a try, and you'll see what I mean!&lt;/p&gt;

&lt;h2&gt;
  
  
  How To Try DbVisualizer 24.2
&lt;/h2&gt;

&lt;p&gt;If you’re already a DbVisualizer user, simply upgrade the software to the latest version. If you need assistance with this process, refer to the &lt;a href="https://www.dbvis.com/docs/ug/getting-started/checking-for-updates/" rel="noopener noreferrer"&gt;guide in the official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to test DbVisualizer 24.2 as a new user, you can &lt;a href="https://www.dbvis.com/download/" rel="noopener noreferrer"&gt;download it for free&lt;/a&gt;. That’ll give you access to essential features, documented in a helpful &lt;a href="https://www.dbvis.com/docs/ug/" rel="noopener noreferrer"&gt;User Guide&lt;/a&gt; and an active &lt;a href="https://support.dbvis.com/support/discussions" rel="noopener noreferrer"&gt;Community forum&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;To access all DbVisualizer features, consider that &lt;strong&gt;you need to upgrade to the Pro plan&lt;/strong&gt;. Upon purchase, you’ll also receive additional guidance from the development team for the first 60 days.&lt;/p&gt;

&lt;p&gt;For enterprises, businesses, and individuals needing priority assistance throughout the subscription period, there’s a &lt;strong&gt;special Pro plan available&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;For more details, visit the &lt;a href="https://www.dbvis.com/pricing/" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; on the official website or explore the &lt;a href="https://www.dbvis.com/feature-dbvisualizer-editions/" rel="noopener noreferrer"&gt;comparison between the Free and Pro plans&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To test all the features of the Pro version, you can also &lt;a href="https://www.dbvis.com/eval/" rel="noopener noreferrer"&gt;evaluate DbVisualizer tool for 21 days via a free trial&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Verdict
&lt;/h2&gt;

&lt;p&gt;In this review, I had the chance to try out DbVisualizer 24.2 and see why it’s a huge leap forward for one of the most loved database clients out there. &lt;/p&gt;

&lt;p&gt;The development team didn’t just build on its already great features—they’ve also polished the user interface to meet modern standards of usability and style. This perfect mix of functionality and design keeps DbVisualizer at the top for both seasoned pros and newcomers in the database world.&lt;/p&gt;

&lt;p&gt;If you’re excited to explore DbVisualizer 24.2 further, check out the links below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.dbvis.com/whatsnew/" rel="noopener noreferrer"&gt;What’s New&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dbvis.com/releasenotes/" rel="noopener noreferrer"&gt;Release Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dbvis.com/docs/24.2/" rel="noopener noreferrer"&gt;User Guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I can confidently say that version 24.2 is the best DbVisualizer has ever been. Whether you’re upgrading from an older version or checking it out for the first time, now is a great time to experience its new features. &lt;/p&gt;

&lt;p&gt;Review score: &lt;strong&gt;9.5/10&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqd7mnwzidcjfap71ttck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqd7mnwzidcjfap71ttck.png" alt="Review score" width="800" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>productivity</category>
      <category>news</category>
    </item>
    <item>
      <title>How to Handle Errors in Next.js for Node With the App Router</title>
      <dc:creator>Antonello Zanini</dc:creator>
      <pubDate>Wed, 11 Sep 2024 10:11:18 +0000</pubDate>
      <link>https://dev.to/appsignal/how-to-handle-errors-in-nextjs-for-node-with-the-app-router-5b65</link>
      <guid>https://dev.to/appsignal/how-to-handle-errors-in-nextjs-for-node-with-the-app-router-5b65</guid>
      <description>&lt;p&gt;Error handling in Next.js is critical to providing a seamless experience to your users even when things go wrong. Without proper error management, users may get confused about what has happened and even leave your site. To avoid that, you must ensure that they receive informative feedback about errors and provide a way to recover from them.&lt;/p&gt;

&lt;p&gt;In this article, you'll see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why the default Next.js error handling logic falls short&lt;/li&gt;
&lt;li&gt;How the &lt;code&gt;error.js&lt;/code&gt; file convention allows you to handle errors in the App Router&lt;/li&gt;
&lt;li&gt;How to define custom logic to deal with both client-side and server-side errors in Next.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's jump right in!&lt;/p&gt;

&lt;h2&gt;
  
  
  Default Error Handling in Next.js
&lt;/h2&gt;

&lt;p&gt;When a server error occurs in a Next.js application, the following '500 Internal server error' page is returned by default:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywln1lf2102vapxuj272.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywln1lf2102vapxuj272.png" alt="Server error" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead, this view is loaded when a fatal client-side error occurs:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qs2p4qbge2zgkgwfb1k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qs2p4qbge2zgkgwfb1k.png" alt="Client-side error" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two huge issues with this behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Generic error messages&lt;/strong&gt;: Both the "Internal server error" and "Application error: a client-side exception has occurred (see the browser console for more information)" messages are too generic for you to understand what happened.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No recovery&lt;/strong&gt;: Users don't have the opportunity to recover from the error and are forced to manually reload the page or leave.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a result, the default Next.js error handling system leads to a pretty bad user experience. Here's why you need to override that with custom error handling logic!&lt;/p&gt;
&lt;h2&gt;
  
  
  How to Handle Errors in Next.js Using the App Router
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nextjs.org/docs/app/building-your-application/routing/error-handling" rel="noopener noreferrer"&gt;Error handling in a Next.js application with the App Router&lt;/a&gt; revolves around the &lt;code&gt;error.js&lt;/code&gt; file convention.&lt;br&gt;
If you aren't familiar with that, &lt;code&gt;error.js&lt;/code&gt; is an optional file inside a &lt;a href="https://nextjs.org/docs/app/building-your-application/routing#file-conventions" rel="noopener noreferrer"&gt;route segment&lt;/a&gt; that exports a UI React component to handle errors gracefully.&lt;/p&gt;

&lt;p&gt;To better understand how this mechanism works, let's analyze two possible scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local and nested errors&lt;/li&gt;
&lt;li&gt;Root-level errors&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Local Error Handling
&lt;/h3&gt;

&lt;p&gt;Assume that you defined an &lt;code&gt;error.js&lt;/code&gt; file in a route segment:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fungw82u20vvf6qs7218x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fungw82u20vvf6qs7218x.png" alt="Route segment" width="304" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When generating the &lt;code&gt;/dashboard&lt;/code&gt; page, the App Router will automatically create a &lt;a href="https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary" rel="noopener noreferrer"&gt;React Error Boundary&lt;/a&gt; and wrap the &lt;code&gt;page.js&lt;/code&gt; component as follows:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foa9tynydk6n2d3emem14.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foa9tynydk6n2d3emem14.png" alt="React error boundary" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, consider if an error occurs in &lt;code&gt;page.js&lt;/code&gt; or any of its nested child route segments. That can be either a client-side or server-side error (as &lt;code&gt;error.js&lt;/code&gt; can deal with any type of error).&lt;/p&gt;

&lt;p&gt;The Error Boundary will intercept the error and render the React component exported by &lt;code&gt;error.js&lt;/code&gt; as a fallback. In particular, the error component will be rendered within the surrounding layout. This means the error component won't take the entire view as it does in the default Next.js error handling logic, but occupy only a portion of the page. That leaves room for recovery, as the layout will maintain its state and remain interactive.&lt;/p&gt;

&lt;p&gt;That's why the component exported by an &lt;code&gt;error.js&lt;/code&gt; file accepts two props:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;error&lt;/code&gt;: Contains the &lt;code&gt;Error&lt;/code&gt; instance with the details of the error that occurred in the client or the server. In production, server-side errors include only generic &lt;code&gt;message&lt;/code&gt; and &lt;code&gt;digest&lt;/code&gt; properties to avoid leaking sensitive information.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reset&lt;/code&gt;: A function to attempt to recover from the error. When called, the function tries to re-render the original component nested in the Error Boundary. If successful, the fallback error component is replaced with the newly rendered result. Otherwise, the fallback error component is left on the page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This error handling mechanism works on a per-route-segment basis, with errors bubbling up to the nearest error boundary. By placing &lt;code&gt;error.js&lt;/code&gt; files at different levels in the nested hierarchy, you can then achieve more or less granular error UI management.&lt;/p&gt;
&lt;h3&gt;
  
  
  Root Error Handling
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;error.js&lt;/code&gt; boundaries can't catch errors occurring in &lt;code&gt;layout.js&lt;/code&gt; or &lt;code&gt;template.js&lt;/code&gt; components of the same segment. Instead, those errors will be intercepted by the &lt;code&gt;error.js&lt;/code&gt; file in the parent segment.&lt;/p&gt;

&lt;p&gt;But what happens if an error is thrown in the root &lt;code&gt;app/layout.js&lt;/code&gt; or &lt;code&gt;app/template.js&lt;/code&gt; component? Since the root &lt;code&gt;app/error.js&lt;/code&gt; boundary won't be able to catch it, Next.js requires a special component. To specifically handle errors in the root &lt;code&gt;layout.js&lt;/code&gt; and &lt;code&gt;template.js&lt;/code&gt; components, you must place a &lt;a href="https://nextjs.org/docs/app/building-your-application/routing/error-handling#handling-errors-in-root-layouts" rel="noopener noreferrer"&gt;&lt;code&gt;global-error.js&lt;/code&gt;&lt;/a&gt; file in the root &lt;code&gt;app&lt;/code&gt; directory. That is nothing more than a variation of &lt;code&gt;error.js&lt;/code&gt;, exporting a component with the same props.&lt;/p&gt;

&lt;p&gt;The main difference with &lt;code&gt;error.js&lt;/code&gt; is that the &lt;code&gt;global-error.js&lt;/code&gt; error boundary wraps the entire application. So its fallback component will replace the entire view and should always contain the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;global-error.js&lt;/code&gt; is the least granular UI component and can be considered the last resort for error management. Although it's essential to ensure complete Next.js error handling, it's unlikely to get rendered often, as the root layout and template components are generally static and less prone to errors.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implement Error Handling in Next.js
&lt;/h2&gt;

&lt;p&gt;Follow the instructions below to handle errors in Next.js with &lt;code&gt;error.js&lt;/code&gt; files.&lt;/p&gt;
&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;To go through this tutorial, you need &lt;a href="https://nodejs.org/en/blog/release/v18.17.0" rel="noopener noreferrer"&gt;Node.js 18.17&lt;/a&gt; or later installed on your machine. In particular, the code snippets below will refer to a Next.js 13+ application with the App Router.&lt;/p&gt;

&lt;p&gt;For access to the entire code of the project you're about to build, &lt;a href="https://github.com/Tonel/custom-error-handling-nextjs" rel="noopener noreferrer"&gt;clone the GitHub repository that supports this article&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Tonel/custom-error-handling-nextjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To test the application, enter the project folder and install the local dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;custom-error-handling-nextjs
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, build the Next.js application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will take a while, so be patient.&lt;/p&gt;

&lt;p&gt;Once it finishes, you can launch the application with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The demo Next.js application with custom error handling logic should now be running at &lt;code&gt;http://localhost:3000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you prefer to start from scratch, launch the &lt;a href="https://nextjs.org/docs/app/api-reference/create-next-app" rel="noopener noreferrer"&gt;&lt;code&gt;create-next-app&lt;/code&gt;&lt;/a&gt; command and follow the instructions to set up a new Next.js application.&lt;br&gt;
Refer to the GitHub repository for the complete code, as the sub-chapters below will cover only the main steps of implementing error handling in Next.js.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create An &lt;code&gt;error.js&lt;/code&gt; File
&lt;/h3&gt;

&lt;p&gt;Add an &lt;code&gt;error.js&lt;/code&gt; file to the root &lt;code&gt;app&lt;/code&gt; directory and initialize it as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./src/app/error.js&lt;/span&gt;

&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./error.module.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reset&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// custom logic (e.g., log the error or send it to an APM service)&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oops&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Oops&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Something&lt;/span&gt; &lt;span class="nx"&gt;went&lt;/span&gt; &lt;span class="nx"&gt;wrong&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;retryButton&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="err"&gt;🔄&lt;/span&gt; &lt;span class="nx"&gt;Retry&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;error.module.css&lt;/code&gt; is a local &lt;a href="https://nextjs.org/docs/app/building-your-application/styling/css#css-modules" rel="noopener noreferrer"&gt;CSS module&lt;/a&gt; containing the styling for the fallback error component. Inside these error UI components, you can also add custom logic, such as logging and application monitoring integrations. In a real-world scenario, you should provide a meaningful explanation for the error.&lt;/p&gt;

&lt;p&gt;Keep in mind that error components must be &lt;a href="https://nextjs.org/docs/app/building-your-application/rendering/client-components" rel="noopener noreferrer"&gt;client components&lt;/a&gt;. If you forget to add the &lt;code&gt;"use client"&lt;/code&gt; instruction, Next.js will raise this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ReactServerComponentsError:
./src/app/error.js must be a Client Component. Add the &lt;span class="s2"&gt;"use client"&lt;/span&gt; directive to the top of the file to resolve this issue.
Learn more: https://nextjs.org/docs/getting-started/react-essentials#client-components
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what the &lt;code&gt;&amp;lt;Error /&amp;gt;&lt;/code&gt; component returned by &lt;code&gt;error.js&lt;/code&gt; looks like:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wvt0hhk72xbo1yrrcyb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wvt0hhk72xbo1yrrcyb.png" alt="Error component" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, it presents the error gracefully to the user and allows them to recover through the "Retry" button.&lt;/p&gt;
&lt;h3&gt;
  
  
  Create a &lt;code&gt;global-error.js&lt;/code&gt; File
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;global-error.js&lt;/code&gt; file in the &lt;code&gt;app&lt;/code&gt; directory and add the following lines to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./src/app/global-error.js&lt;/span&gt;

&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GlobalError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reset&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// custom logic (e.g., log the error or send it to an APM service)&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;globalErrorContainer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;globalErrorDiv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;globalError500&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Internal&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nx"&gt;Home&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Link&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/body&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/html&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the application crashes due to a fatal error in the root layout or template files, a &lt;code&gt;reset()&lt;/code&gt; call is unlikely to resolve the situation.&lt;br&gt;
As a recovery strategy, you should instead add a link to the home page or force a page reload with &lt;code&gt;window.location.reload();&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note that Next.js will ignore any CSS module or global file imports in &lt;code&gt;global-error.js&lt;/code&gt;.&lt;br&gt;
To properly style this component, add the required CSS classes to your &lt;a href="https://nextjs.org/docs/app/building-your-application/styling/css#global-styles" rel="noopener noreferrer"&gt;global root CSS file&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's how the &lt;code&gt;&amp;lt;GlobalError /&amp;gt;&lt;/code&gt; component appears:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5do35jvbwjgj0u1sz2uq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5do35jvbwjgj0u1sz2uq.png" alt="Global error component" width="800" height="522"&gt;&lt;/a&gt;&lt;br&gt;
In a real-world site, replace "Internal Error" with a better error message.&lt;/p&gt;

&lt;p&gt;Bear in mind that Next.js ignores the &lt;code&gt;global-error.js&lt;/code&gt; file in development mode.&lt;br&gt;
In that scenario, a fatal error in the root layout or template component triggers an overview with detailed debugging information, like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgsfzxye3gnqaq5pia36z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgsfzxye3gnqaq5pia36z.png" alt="Fatal error" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To test the error component returned by &lt;code&gt;global-error.js&lt;/code&gt;, you must first build your application and then run it with &lt;code&gt;npm run start&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Get Ready to Trigger Errors
&lt;/h3&gt;

&lt;p&gt;To verify that &lt;code&gt;error.js&lt;/code&gt; and &lt;code&gt;global-error.js&lt;/code&gt; work as expected, define a &lt;code&gt;&amp;lt;ErrorButton /&amp;gt;&lt;/code&gt; component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ./src/app/components/ErrorButton.js&lt;/span&gt;

&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./error-button.module.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ErrorButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;raiseError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setRaiseError&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raiseError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// "a" is undefined so "props.a.b" will result in an error&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
        &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;errorButton&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setRaiseError&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When clicked, &lt;code&gt;raiseError&lt;/code&gt; becomes &lt;code&gt;true&lt;/code&gt; and a client-side error is thrown.&lt;/p&gt;

&lt;p&gt;Now, nest that component in &lt;code&gt;layout.js&lt;/code&gt; to trigger global errors and in &lt;code&gt;page.js&lt;/code&gt; to trigger local errors.&lt;br&gt;
Here's what the home page will contain:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqjgkhimgqjfgdh9sg0a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqjgkhimgqjfgdh9sg0a.png" alt="Home page" width="800" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wonderful! You're now ready to test the custom Next.js error handling logic!&lt;/p&gt;

&lt;h3&gt;
  
  
  Put It All Together
&lt;/h3&gt;

&lt;p&gt;Launch the complete Next.js application as described in the &lt;em&gt;Prerequisites&lt;/em&gt; section of this post.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;http://localhost:3000&lt;/code&gt; in the browser and click the "Simulate Local Error" button:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrig99webpc6ma8bgr41.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrig99webpc6ma8bgr41.gif" alt="Local error" width="1412" height="772"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;Error /&amp;gt;&lt;/code&gt; component will be rendered in the global layout UI as desired.&lt;br&gt;
By clicking "Retry," the App Router will re-render the &lt;code&gt;page.js&lt;/code&gt; component from scratch.&lt;br&gt;
This means that the &lt;code&gt;raiseError&lt;/code&gt; state property from &lt;code&gt;&amp;lt;ErrorButton /&amp;gt;&lt;/code&gt; will be assigned to &lt;code&gt;false&lt;/code&gt; and the error will be recovered.&lt;/p&gt;

&lt;p&gt;Now, click the "Simulate Global Error" button:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw6unhjnkcnllerlmhp36.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw6unhjnkcnllerlmhp36.gif" alt="Global error button" width="1412" height="772"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This time, the &lt;code&gt;&amp;lt;GlobalError /&amp;gt;&lt;/code&gt; component will replace the entire view as expected.&lt;/p&gt;

&lt;p&gt;Et voilà! You are now a Next.js error handling master!&lt;/p&gt;

&lt;h2&gt;
  
  
  Error Handling in Next.js: Best Practices
&lt;/h2&gt;

&lt;p&gt;Handling errors in Next.js can be challenging, especially if you don't follow these best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always define a &lt;code&gt;global-error.js&lt;/code&gt; file&lt;/strong&gt;: Even though the &lt;code&gt;error.js&lt;/code&gt; boundaries will catch most errors, your project must have a &lt;code&gt;global-error.js&lt;/code&gt; file to make sure that even fatal errors at the root layer are handled gracefully.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't use &lt;code&gt;global-error.js&lt;/code&gt; as a replacement for &lt;code&gt;error.js&lt;/code&gt;&lt;/strong&gt;: Even if your project has &lt;code&gt;global-error.js&lt;/code&gt; in place, you should define a root &lt;code&gt;error.js&lt;/code&gt; to handle non-root errors within the global layout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add recovery logic&lt;/strong&gt;: Your users should always have the opportunity to recover from the error situation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid leaking implementation details&lt;/strong&gt;: The error message should be detailed enough to explain what happened to the user. At the same time, it shouldn't provide implementation or sensitive information to potential attackers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up: Next.js Error Handling Made Easier!
&lt;/h2&gt;

&lt;p&gt;In this blog post, you saw how to introduce custom error handling logic to your Next.js application using the App Router, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How Next.js handles errors by default&lt;/li&gt;
&lt;li&gt;How to define error UI components in the App Router&lt;/li&gt;
&lt;li&gt;The best practices for secure and effective Next.js error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading, and see you in the next one!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you liked this post, &lt;a href="https://blog.appsignal.com/javascript-sorcery" rel="noopener noreferrer"&gt;subscribe to our JavaScript Sorcery list&lt;/a&gt; for a monthly deep dive into more magical JavaScript tips and tricks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.P.S. If you need an APM for your Node.js app, go and &lt;a href="https://www.appsignal.com/nodejs" rel="noopener noreferrer"&gt;check out the AppSignal APM for Node.js&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Expand Content Reach Using AI for SEO and Translation in Your CMS</title>
      <dc:creator>Antonello Zanini</dc:creator>
      <pubDate>Thu, 22 Aug 2024 15:38:48 +0000</pubDate>
      <link>https://dev.to/apostrophecms/expand-content-reach-using-ai-for-seo-and-translation-in-your-cms-2jfb</link>
      <guid>https://dev.to/apostrophecms/expand-content-reach-using-ai-for-seo-and-translation-in-your-cms-2jfb</guid>
      <description>&lt;p&gt;In recent months, the way we approach our work has changed significantly. AI has become central to many workflows, driving productivity, enhancing efficiency, and providing valuable insights. The CMS industry is no exception to this trend. That is why why Apostrophe recently released two new extensions that leverage AI for SEO and translation.&lt;/p&gt;

&lt;p&gt;In this article, you will discover the latest developments from Apostrophe in the AI space. You will learn what the new SEO Assistant and Automatic Translation extensions are, and see how they can streamline your SEO metadata definition and content translation processes with just a few clicks.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Applications in the CMS Industry: From Ideas to Reality
&lt;/h2&gt;

&lt;p&gt;In our article published in mid-2023, we examined &lt;a href="https://apostrophecms.com/blog/how-ai-is-transforming-the-cms-industry" rel="noopener noreferrer"&gt;how AI could transform the CMS industry&lt;/a&gt;. During that discussion, we introduced the &lt;a href="https://apostrophecms.com/extensions/ai-helper" rel="noopener noreferrer"&gt;AI Helper&lt;/a&gt; extension to generate images and rich text directly from a prompt within ApostropheCMS.&lt;/p&gt;

&lt;p&gt;In the months that followed, those insights were validated and evolved into concrete developments. These ideas were incorporated into &lt;a href="https://portal.productboard.com/apostrophecms/1-product-roadmap/tabs/2-planned" rel="noopener noreferrer"&gt;Apostrophe's roadmap&lt;/a&gt; and have now materialized into two new AI-powered extensions for Apostrophe Pro. &lt;/p&gt;

&lt;p&gt;Let’s dive into these exciting new features!&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing New Apostrophe AI-Powered Plugins: SEO Assistant and Automatic Translation
&lt;/h2&gt;

&lt;p&gt;These are two new extensions in the Apostrophe AI offerings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://apostrophecms.com/extensions/seo-assistant" rel="noopener noreferrer"&gt;SEO Assistant&lt;/a&gt;: Automatically generates SEO page metadata through AI.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://apostrophecms.com/extensions/automatic-translation" rel="noopener noreferrer"&gt;Automatic Translation&lt;/a&gt;: Provides AI-driven translation of documents (pages and pieces) for content localization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Both extensions are exclusive to &lt;a href="https://apostrophecms.com/pro" rel="noopener noreferrer"&gt;Apostrophe Pro&lt;/a&gt; and &lt;a href="http://apostrophecms.com/assembly" rel="noopener noreferrer"&gt;Apostrophe Assembly&lt;/a&gt; users. These premium versions of Apostrophe offers advanced features tailored for both developers and editors.&lt;/p&gt;

&lt;p&gt;As you are about to learn, both extensions work together to optimize your site’s reach. To better showcase SEO Assistant and Automatic Translation, they will be integrated into the site showcased in our tutorial on &lt;a href="https://apostrophecms.com/blog/how-to-build-an-ecommerce-website-with-apostrophecms" rel="noopener noreferrer"&gt;how to create an e-commerce platform with Apostrophe&lt;/a&gt;. If you are not familiar with that article, &lt;a href="https://vimeo.com/907612831" rel="noopener noreferrer"&gt;you can see an example of in the following video.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using AI for SEO Optimization in Your CMS
&lt;/h2&gt;

&lt;p&gt;One of the most tedious, yet essential, tasks when handling the SEO of a website is defining the meta title and SEO description of a webpage. This information is key because search engines like Google use it to generate the SERP (&lt;a href="https://www.semrush.com/blog/serp/" rel="noopener noreferrer"&gt;Search&lt;/a&gt;&lt;a href="https://www.semrush.com/blog/serp/" rel="noopener noreferrer"&gt; Engine Results Page&lt;/a&gt;) entry associated with your page, as in the following image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60y8x6kgdln7nugap6zm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60y8x6kgdln7nugap6zm.png" alt="Example of how SEO metadata is used in search results." width="677" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is even more important when considering that over &lt;a href="https://www.brightedge.com/resources/research-reports/channel_share" rel="noopener noreferrer"&gt;50% of traffic to business websites&lt;/a&gt; comes from organic search (i.e., SEO).&lt;/p&gt;

&lt;p&gt;When specifying the SEO meta title and description of a page, there are a few best practices to keep in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Both the title and description should be concise, actionable, and to the point. They should also include relevant keywords to help search engines rank your page higher.&lt;/li&gt;
&lt;li&gt;The meta title should be between 50 to 60 characters long to ensure that search engines do not truncate it in the SERP snippet.&lt;/li&gt;
&lt;li&gt;The meta description should ideally be between 50 to 160 characters, with an optimal length of around 150/160 characters. For more information, check out Google's &lt;a href="https://developers.google.com/search/docs/appearance/snippet" rel="noopener noreferrer"&gt;best practices of meta descriptions&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem is that writing effective SEO titles and descriptions is often seen as a boring task. Ask your editors, and they will likely tell you that they prefer to craft great content rather than focusing on SEO details. &lt;/p&gt;

&lt;p&gt;Additionally, SEO optimization is typically the final step in the publication process. As a result, some editors might overlook it or not give it the attention it deserves to push the page online.&lt;/p&gt;

&lt;p&gt;The good news is that SEO title and description values are directly related to the page's content, which opens the door to leveraging AI for SEO. Specifically, the AI-powered SEO Assistant extension can analyze the page content to generate effective meta titles and descriptions for you. &lt;/p&gt;

&lt;p&gt;Let’s explore how this Apostrophe Pro extension works in the section below!&lt;/p&gt;

&lt;h3&gt;
  
  
  SEO Assistant in Action  
&lt;/h3&gt;

&lt;p&gt;Follow the &lt;a href="https://apostrophecms.com/extensions/seo-assistant#installation" rel="noopener noreferrer"&gt;official integration guide&lt;/a&gt; to install and configure the SEO Assistant extension in your Apostrophe application. As of this writing, the prerequisites for using the SEO Assistant are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Apostrophe 4 application with the &lt;a href="https://apostrophecms.com/extensions/seo-tools-3" rel="noopener noreferrer"&gt;SEO Tools&lt;/a&gt; and other required extensions installed, as described in the &lt;a href="https://apostrophecms.com/extensions/seo-assistant" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://openai.com/index/openai-api/" rel="noopener noreferrer"&gt;OpenAI API key&lt;/a&gt; or a custom integration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this example, we will use OpenAI as the AI provider, but keep in mind that SEO Assistant also supports &lt;a href="https://apostrophecms.com/extensions/seo-assistant#custom-provider" rel="noopener noreferrer"&gt;custom AI providers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, assume you just created a special web page called “Summer Collection 2024" to promote your clothing line for the summer season. The final step before publishing it is to define the meta title and description.&lt;/p&gt;

&lt;p&gt;To reach the page edit modal, click on “Pages” in the top left menu, locate the “Summer Collection 2024” draft page, and select the “Edit” option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapos-website-prod.s3.us-east-1.amazonaws.com%2Fattachments%2Fclzvommpg0afq0bnomc9mmddx-6rxeaxbt.full.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapos-website-prod.s3.us-east-1.amazonaws.com%2Fattachments%2Fclzvommpg0afq0bnomc9mmddx-6rxeaxbt.full.gif" alt="Example of how you get to an edit screen from the page manager." width="8" height="3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navigate to the “SEO” tab, where you will see that both the “Title” and “Description” fields are empty:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1j1pa5hrn9emgc8jf25b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1j1pa5hrn9emgc8jf25b.png" alt="Example of the SEO metadata fields when the SEO assistant is installed." width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Normally, you would have to manually fill in those fields, but no worries—your AI-powered SEO Assistant is here to help!&lt;/p&gt;

&lt;p&gt;Click on the button to access the SEO Assistant options. Select “Make a suggestion based on page content” to instruct AI to generate the selected meta field using the content found on the page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fko2xc7zhgfs9u4iv0dhx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fko2xc7zhgfs9u4iv0dhx.gif" alt="Example of using the SEO Assistant to suggest a page title." width="702" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you have three options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Use this suggestion&lt;/strong&gt;: Populate the field with the AI-generated text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjfc1mxwgqm05wbj1ijo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjfc1mxwgqm05wbj1ijo.gif" alt="An example of choosing to use an SEO suggestion and applying that text to be part of the form field that will be saved." width="702" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Try again&lt;/strong&gt;: Request an alternative suggestion on the fly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzgv5vkeaceb3y30s6ge6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzgv5vkeaceb3y30s6ge6.gif" alt="An example of regenerating an SEO title property with the SEO Assistant." width="702" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Edit prompt&lt;/strong&gt;: Manually configure the prompt used to query the AI for SEO meta field generation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjfa6m7x5unwqv2t44ktm.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjfa6m7x5unwqv2t44ktm.gif" alt="An example of editing the prompt to tailor the SEO title suggestion." width="702" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly, you can generate an SEO meta description with a couple of clicks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhmu2j3m9lkxbwb71m5g8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhmu2j3m9lkxbwb71m5g8.gif" alt="Using the SEO Assistant to suggest content for the Description field." width="702" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The above example focused on a page, but SEO Assistant also works for individual &lt;a href="https://docs.apostrophecms.org/guide/pieces.html" rel="noopener noreferrer"&gt;content pieces&lt;/a&gt;. Plus, it can be configured to produce results of a &lt;a href="https://apostrophecms.com/extensions/seo-assistant#additional-configuration" rel="noopener noreferrer"&gt;given minimum length&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Amazing! SEO Assistant generated a captivating meta title and description for you in just a fraction of a second. It only remains to click “Publish.”&lt;/p&gt;

&lt;p&gt;As shown here, using AI for SEO simplifies metadata optimization and removes the tediousness associated with the task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating AI for Translation in your CMS
&lt;/h2&gt;

&lt;p&gt;Localizing a website into several languages is critical to reaching a global audience. As of 2023, approximately 1.46 billion people are estimated to speak English worldwide. That represents about &lt;a href="https://wordsrated.com/how-many-people-speak-english/" rel="noopener noreferrer"&gt;18.07% of the global population&lt;/a&gt;—less than 1 in 5 people.&lt;/p&gt;

&lt;p&gt;As you can imagine, localization becomes essential when targeting local markets in regions such as Asia, Europe, or Latin America. Thankfully, Apostrophe comes with comprehensive &lt;a href="https://docs.apostrophecms.org/guide/localization/overview.html" rel="noopener noreferrer"&gt;support for localization&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When translating a website, a common practice is to start with layout elements like menus, footers, and headers. Although this initial step can be time-consuming, it only needs to be done once as those elements are shared across all pages. That might create the misconception that translating a website into a new language is a quick task. Well, that is not true!&lt;/p&gt;

&lt;p&gt;The real challenge arises when realizing that you have to translate all pages, content, and media files. This process can take up to months, especially on large sites. Luckily, you can now automate the task by using AI for translation.&lt;/p&gt;

&lt;p&gt;Let’s see how the Apostrophe Pro Automatic Translation extension can help you translate pages and pieces with just a few clicks!&lt;/p&gt;

&lt;h3&gt;
  
  
  Automatic Translation in Action  
&lt;/h3&gt;

&lt;p&gt;Follow the &lt;a href="https://apostrophecms.com/extensions/automatic-translation#installation" rel="noopener noreferrer"&gt;integration guide&lt;/a&gt; to install and set up the Automatic Translation extension in your Apostrophe application.&lt;/p&gt;

&lt;p&gt;Automatic Translation comes with built-in support for &lt;a href="https://cloud.google.com/translate" rel="noopener noreferrer"&gt;Google Cloud Translation&lt;/a&gt; and &lt;a href="https://www.deepl.com/" rel="noopener noreferrer"&gt;DeepL&lt;/a&gt; as AI translation providers (with support for &lt;a href="https://azure.microsoft.com/en-us/products/ai-services/ai-translator" rel="noopener noreferrer"&gt;Azure AI Translator&lt;/a&gt; coming soon). To integrate them, you just need a valid API key. Keep in mind that the extension also supports &lt;a href="https://apostrophecms.com/extensions/seo-assistant#custom-provider" rel="noopener noreferrer"&gt;custom providers&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In this example, we will use DeepL, but any other AI translation provider will do.&lt;/p&gt;

&lt;p&gt;Suppose you already configured the &lt;em&gt;es&lt;/em&gt; locale in your Apostrophe e-commerce project and translated all layout elements. The next step is to translate all content from English to Spanish, which will make your site more accessible to the &lt;a href="https://www.atalayar.com/en/articulo/culture/spanish-continues-grow-and-has-almost-500-million-native-speakers-according-cervantes/20221026154937158810.html" rel="noopener noreferrer"&gt;500 million native Spanish speakers worldwide&lt;/a&gt;. &lt;em&gt;¡Empezamos!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is what the English version of the e-commerce site currently looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9twulmu2vrie8395gqxu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9twulmu2vrie8395gqxu.gif" alt="Scrolling through an example of the ecommerce starter kit." width="200" height="96"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal is to have the entire site translated into Spanish.&lt;/p&gt;

&lt;p&gt;The first document to translate is undoubtedly the home page. To do that, click on "Pages" in the top left menu, locate the "Home" page, and select the "Localize…" option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzdtz53r27l25phi1mw1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzdtz53r27l25phi1mw1.png" alt="Example of localizing a page via the context menu item." width="800" height="249"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will open the Automatic Translation modal below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fup9xb3vkdxtos3bnbmdv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fup9xb3vkdxtos3bnbmdv.png" alt="Screenshot of the Localize Content modal." width="659" height="723"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A wizard will guide you through the process of configuring how Apostrophe will use AI for translation. Specifically, you will be asked to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select the content to localize. The available options are the current document, the current document and its related documents (such as images and any other referenced documents), or only the related documents.&lt;/li&gt;
&lt;li&gt;Choose the locales to translate the content into.&lt;/li&gt;
&lt;li&gt;Specify the related document types to localize, if you chose to translate them in step 1. You can also decide whether to translate only new related documents or to overwrite existing translated documents. Select the “Translate text content option“ for automatic AI translation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The home page depends on some related documents. Therefore, the recommended option is "This document and related documents":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ndn7txkzi2wyoarjv3h.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6ndn7txkzi2wyoarjv3h.gif" alt="An example of how the Localize Content UI is used in the automatic translation workflow." width="670" height="738"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the last step of the wizard, you can select which types of related documents you want to localize. Apostrophe will automatically translate them together with the current document.&lt;/p&gt;

&lt;p&gt;The following notifications will inform you that the current document and the selected related documents were translated successfully:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fden4pfl18m3x5lsn3vty.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fden4pfl18m3x5lsn3vty.png" alt="Example of notifications received after successful localization and translation of content." width="703" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Visit the home page of the Spanish version of your e-commerce site, and you will now see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqvx8jljdge856reqgmb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqvx8jljdge856reqgmb.gif" alt="An example of scrolling through the ecommerce page after translating it to spanish." width="600" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wow, a good start! However, notice that there are no products or categories on the page. This is because they need to be translated as well.&lt;/p&gt;

&lt;p&gt;Before translating them, you may find that you are not satisfied with the translation results provided by the AI. No problem, you can always edit the localized documents using Apostrophe's editing capabilities:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qkzmdxhvzkpp4zw4nsd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qkzmdxhvzkpp4zw4nsd.png" alt="Example of editing a page's settings in the modal after translating it into Spanish." width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The translated pages will be saved as drafts in the selected locale(s). This way, a content manager will have to manually review and approve them for publication. Also, Apostrophe will automatically mark the AI-translated fields, as they require additional attention from your editors. &lt;/p&gt;

&lt;p&gt;Also, keep in mind that Automatic Translation also translates the SEO meta title and description into the target language. To verify that, explore the “SEO” tab of the “Summer Collection 2024” page mentioned in the previous chapter:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5ad7m6fz64d1it2jmo2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo5ad7m6fz64d1it2jmo2.png" alt="Example of UI for SEO fields that have automated translations applied." width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Observe how both the meta title and the SEO description were translated into Spanish.&lt;/p&gt;

&lt;p&gt;Awesome, you are ready to translate products and categories!&lt;/p&gt;

&lt;p&gt;Click on "Content" in the top left menu, select a content category, and follow the same procedure as above to translate each piece:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wipn2v56q0fwrtjf807.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wipn2v56q0fwrtjf807.png" alt="Example of the localize menu action on a Product piece type." width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Considering that pieces often involve or are connected to other documents, you can choose the “this document and related documents” option to speed up the translation process.&lt;/p&gt;

&lt;p&gt;Use AI to translate all pages and pieces in your project, and this is the end result you will get in just a few minutes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffu4h5u3zee5gmorlbig6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffu4h5u3zee5gmorlbig6.gif" alt="The finished product after translating the ecommerce page and all of the product categories that are included." width="480" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While it may not be perfect, this is definitely impressive!&lt;/p&gt;

&lt;p&gt;Similarly, this is what an AI-translated page product looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4xul0mxgr1l841blf4l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh4xul0mxgr1l841blf4l.png" alt="Example product show page after it's been translated to spanish via the localization capability." width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Automatic Translation offers a wide range of options and customizations to tailor the AI content translation experience to your needs. For more details, see the &lt;a href="https://apostrophecms.com/extensions/automatic-translation#usage" rel="noopener noreferrer"&gt;developer documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As proven here, leveraging AI for translation significantly accelerates the content localization process. That creates a tremendous opportunity to make your site accessible to millions of users worldwide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of SEO Assistant and Automatic Translation Extensions
&lt;/h2&gt;

&lt;p&gt;A few months ago, it was merely a guess, but now there is little doubt that integrating AI directly into a CMS introduces substantial benefits. Specifically, using AI for SEO and translation provides these advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Expanding content reach&lt;/strong&gt;: AI-driven SEO and translation help to reach a broader audience. Enhanced SEO rankings increase your site's visibility, while localized content resonates with diverse audiences in different regions, significantly boosting the chances of attracting new users to your pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improving productivity&lt;/strong&gt;: Tasks that once required hours of meticulous work can now be completed in seconds. AI handles the heavy lifting, leaving you to refine and personalize the generated content. This shift reduces time spent on manual tasks and helps you concentrate on what truly matters, such as creating outstanding user experiences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Saving money&lt;/strong&gt;: By delegating specialized tasks like SEO optimization and translation to AI, you may no longer need to hire a team of dedicated experts in those fields. Such a cost-effective strategy enables you to allocate resources more efficiently while still achieving high-quality results, ultimately saving your business money.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The above improvements are made possible by SEO Assistant and Automatic Translation, the new AI-powered Apostrophe Pro extensions. &lt;/p&gt;

&lt;p&gt;These are just a few of the several powerful features available in the &lt;a href="http://apostrophecms.com/pricing" rel="noopener noreferrer"&gt;Pro and Assembly tiers&lt;/a&gt; of Apostrophe, which also include &lt;a href="https://apostrophecms.com/blog/how-to-use-apostrophe-s-advanced-permission-to-manage-editing-rights" rel="noopener noreferrer"&gt;advanced permissions&lt;/a&gt;, document version history, and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we looked at what the new AI-powered SEO Assistant and Automatic Translation extensions have to offer. With AI integrated directly into Apostrophe, you can now generate SEO metadata and translate content into any language within seconds and a handful of clicks.&lt;/p&gt;

&lt;p&gt;While the current results are already spectacular, this is just the beginning. The Apostrophe team is continually working to discover new and effective AI integrations, and you should be excited about what the future holds.&lt;/p&gt;

&lt;p&gt;To get a preview of what's next for Apostrophe, keep an eye on our &lt;a href="https://portal.productboard.com/apostrophecms/1-product-roadmap/tabs/2-planned" rel="noopener noreferrer"&gt;product roadmap&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cms</category>
      <category>translation</category>
      <category>seo</category>
    </item>
    <item>
      <title>An Introduction to Unit Testing in Node.js</title>
      <dc:creator>Antonello Zanini</dc:creator>
      <pubDate>Thu, 22 Aug 2024 15:13:50 +0000</pubDate>
      <link>https://dev.to/appsignal/an-introduction-to-unit-testing-in-nodejs-4n7h</link>
      <guid>https://dev.to/appsignal/an-introduction-to-unit-testing-in-nodejs-4n7h</guid>
      <description>&lt;p&gt;Unit tests are essential to verify the behavior of small code units in a Node.js application. This leads to clearer design, fewer bugs, and better adherence to business requirements. That's why Test-Driven Development (TDD) and Behavior-Driven Development (BDD) have become so popular in the backend development community.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll dive into unit testing and understand why it's needed in your backend. We'll then learn how a unit test is structured and explore the most widely used libraries for unit testing in Node.js.&lt;/p&gt;

&lt;p&gt;It's time to become a Node.js unit testing expert!&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Node.js Unit Testing?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Unit_testing" rel="noopener noreferrer"&gt;Unit testing&lt;/a&gt; is a software testing practice in which individual units of an application are tested to ensure they behave as expected. In particular, a unit test is a short and isolated test that verifies the correctness of a specific piece of code — typically a function or method.&lt;/p&gt;

&lt;p&gt;Thus, Node.js unit testing involves writing tests to validate the functionality of individual modules in your backend. This includes testing API endpoints, database interactions, and business logic functions to ensure robustness.&lt;/p&gt;

&lt;p&gt;A CI/CD pipeline usually runs unit tests to verify the integrity of a deployment, ensuring that a Node.js application stays reliable, even after code changes are made pre-production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Write Unit Tests in Node.js?
&lt;/h2&gt;

&lt;p&gt;Now that you know what unit testing is in Node.js, you may wonder why your backend needs unit tests.&lt;br&gt;
Here are three good reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;To discover bugs earlier&lt;/strong&gt;: Unit tests allow you to detect bugs in APIs during development or in the CI/CD deployment pipeline. By identifying issues earlier, you can fix them immediately and reduce the likelihood of runtime errors. Thanks to higher code coverage, your users will have a better experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;To increase code quality&lt;/strong&gt;: Unit testing serves as a form of documentation for your code. That's because each Node.js unit test provides a clear example of how a specific function should behave. By writing tests that cover different scenarios, you can verify that your backend is resilient to unexpected inputs. This leads to higher code quality and increased maintainability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;To improve your architecture design&lt;/strong&gt;: To make the codebase of your backend testable, you must organize your codebase accordingly. This encourages designing a modular and decoupled Node.js architecture. As a rule of thumb, you don't want to write complex components that are difficult to test.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  What a Unit Test in Node.js Looks Like
&lt;/h2&gt;

&lt;p&gt;Most Node.js test suites follow the &lt;a href="https://en.wikipedia.org/wiki/Behavior-driven_development" rel="noopener noreferrer"&gt;BDD specification style&lt;/a&gt;, which revolves around the &lt;code&gt;describe()&lt;/code&gt; and &lt;code&gt;it()&lt;/code&gt; functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user service functions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should retrieve the user with the given ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should update the information of an existing user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// other it() unit tests...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this convention, a single JavaScript test file typically contains multiple unit tests represented as &lt;code&gt;it()&lt;/code&gt; functions. These test functions can then be encapsulated within a &lt;code&gt;describe()&lt;/code&gt; block for clarity and logical organization.&lt;/p&gt;

&lt;p&gt;Let's now explore the logical sections a Node.js unit test usually consists of.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Setup
&lt;/h3&gt;

&lt;p&gt;Here, you focus on preparing an environment to run unit tests in isolation. In a Node.js application, this involves setting up the right environment variables and a sample database. This step aims to ensure that tests produce predictable and consistent results.&lt;/p&gt;

&lt;p&gt;To achieve that, the BDD specification style provides the following two hooks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;before()&lt;/code&gt;: Runs once before all the tests in &lt;code&gt;describe()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;beforeEach()&lt;/code&gt;: Runs before each individual unit test case in &lt;code&gt;describe()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These hooks are useful to initialize resources and shared variables for tests.&lt;/p&gt;

&lt;p&gt;To ensure that Node.js unit tests run in isolation, you may also need to rely on the following techniques:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mocking&lt;/strong&gt;: Creating fake objects to simulate external dependencies that the functions you're testing depend on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stubbing&lt;/strong&gt;: Overriding specific functions or methods with default behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spying&lt;/strong&gt;: Observing function calls to track their behavior during testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Testing
&lt;/h3&gt;

&lt;p&gt;Testing directly executes the lines of code you want to test to exercise the functionality you're testing and observe its behavior.&lt;br&gt;
Specific lines of code — commonly business logic functions — are called to retrieve data or perform operations. In other words, you provide inputs to a function and retrieve the resulting output.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Assertion
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Assertion_(software_development)" rel="noopener noreferrer"&gt;Assertions&lt;/a&gt; are predicates that make sure your code behaves as intended. The results you obtain after testing are compared with the expected results.&lt;/p&gt;

&lt;p&gt;Node.js test suite libraries come with an assertion engine that makes it easier to write more expressive and readable assertions. You get functions to compare values and verify conditions in a structured way.&lt;/p&gt;

&lt;p&gt;This step is critical to validate the correctness of the code being tested. It checks whether the tested code produces the correct results on the given inputs and conditions.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Cleanup
&lt;/h3&gt;

&lt;p&gt;This (optional) phase of a Node.js unit test releases the allocated resources from the preparation step. The BDD specification style provides two hooks for cleanup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;after()&lt;/code&gt;: Runs once after all unit tests in &lt;code&gt;describe()&lt;/code&gt; have been executed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;afterEach()&lt;/code&gt;: Runs after each individual unit test defined in &lt;code&gt;describe()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code in these hooks does tasks like closing database connections, deleting temporary files, and resetting temporary settings.&lt;/p&gt;
&lt;h2&gt;
  
  
  Popular Libraries to Perform Unit Testing in Node.js
&lt;/h2&gt;

&lt;p&gt;Suppose you have a sample Node.js endpoint that returns the Fibonacci series up to &lt;code&gt;n&lt;/code&gt; terms, where &lt;code&gt;n&lt;/code&gt; is a query parameter. This is what your Express server may look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateFibonacciSeries&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./services/math&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/math/fibonacci/:n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fibonacciSeries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateFibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;fibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fibonacciSeries&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server is running on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The business logic to calculate the Fibonacci series is encapsulated in the following function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// services/math.js&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateFibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fibonacciSeries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;fibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;generateFibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it's time to explore the most popular Node.js unit testing frameworks and libraries using this example. We'll take a look at each tool, its pros and cons, and how to use it to test the above Node.js business logic function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mocha
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/mochajs/mocha" rel="noopener noreferrer"&gt;Mocha&lt;/a&gt; is a simple and flexible JavaScript testing framework for browser and Node.js applications. Unlike other testing frameworks, it takes a minimalist approach and relies on external libraries for key tasks. It uses &lt;a href="https://sinonjs.org/" rel="noopener noreferrer"&gt;Sinon&lt;/a&gt; for handling spies, stubs, and mocks, and &lt;a href="https://www.chaijs.com/" rel="noopener noreferrer"&gt;Chai&lt;/a&gt; as the assertion engine. Mocha is extensible through many plugins and can integrate with most test runners.&lt;/p&gt;

&lt;p&gt;This is how you can use Mocha to write a Node.js unit test for &lt;code&gt;generateFibonacciSeries()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assert&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateFibonacciSeries&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../services/math&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;verify that math service works&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;the Fibonacci series should return [0, 1, 1, 2, 3, 5] when n is 6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedFibonacciSeries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actualFibonacciSeries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateFibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actualFibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expectedFibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;📈 npm weekly downloads&lt;/strong&gt;: Over 6.8 million&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⭐ GitHub stars&lt;/strong&gt;: 22.4k+&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👍 Pros&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple setup and lightweight approach to unit testing.&lt;/li&gt;
&lt;li&gt;Highly extensible via plugins for assertions, reporters, and mock libraries.&lt;/li&gt;
&lt;li&gt;Great for testing asynchronous operations.&lt;/li&gt;
&lt;li&gt;Chai's assertion API is extremely rich.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;👎 Cons&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuration overhead when setting up different assertion libraries, mocking tools, and other plugins.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Jest
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/jestjs/jest" rel="noopener noreferrer"&gt;Jest&lt;/a&gt; is an all-in-one JavaScript testing framework with a focus on simplicity. It supports projects using Babel, TypeScript, Node.js, React, Angular, Vue, and more. To maximize performance, it runs tests in parallel on isolated processes. Jest comes with an integrated assertion engine based on the &lt;a href="https://jestjs.io/docs/expect" rel="noopener noreferrer"&gt;&lt;code&gt;expect()&lt;/code&gt;&lt;/a&gt; function and requires zero configuration for most projects.&lt;/p&gt;

&lt;p&gt;Here's how to use Jest to write a unit test for &lt;code&gt;generateFibonacciSeries()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateFibonacciSeries&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../src/services/math&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;verify that math service works&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should return [0, 1, 1, 2, 3, 5] for Fibonacci series when n is 6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedFibonacciSeries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actualFibonacciSeries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateFibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actualFibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedFibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;📈 npm weekly downloads&lt;/strong&gt;: Over 20.2 million&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⭐ GitHub stars&lt;/strong&gt;: 43.5k+&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👍 Pros&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ready to go after installation, with no configuration overhead.&lt;/li&gt;
&lt;li&gt;Comes with built-in mocking, snapshot testing, and code coverage out of the box.&lt;/li&gt;
&lt;li&gt;Optimized for speed with parallel test execution.&lt;/li&gt;
&lt;li&gt;Well-documented, with a large and active community providing support.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;👎 Cons&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installs many dependencies during the initial setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Jasmine
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/jasmine/jasmine" rel="noopener noreferrer"&gt;Jasmine&lt;/a&gt; is a BDD testing framework for Node.js projects and other JavaScript applications. The library comes with no external dependencies, so it is simple to use with a low overhead. It's the oldest of the three tools we've covered, having been around since 2010. Jasmine runs on any JavaScript platform and is compatible with other testing libraries.&lt;/p&gt;

&lt;p&gt;See Jasmine in action when writing a Node.js unit test for &lt;code&gt;generateFibonacciSeries()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;generateFibonacciSeries&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../src/services/math&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;verify that math service works&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should return [0, 1, 1, 2, 3, 5] for Fibonacci series when n is 6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedFibonacciSeries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actualFibonacciSeries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateFibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actualFibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedFibonacciSeries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the &lt;code&gt;expect()&lt;/code&gt; functions of Jest and Jasmine have a similar syntax, but they are not the same function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📈 npm weekly downloads&lt;/strong&gt;: Over 3.8 million&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⭐ GitHub stars&lt;/strong&gt;: 15.7k+&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👍 Pros&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Offers built-in support for assertions, mocks, and spies.&lt;/li&gt;
&lt;li&gt;Provides a readable and expressive syntax for writing tests.&lt;/li&gt;
&lt;li&gt;Works in both browser and Node.js environments with easy configuration.&lt;/li&gt;
&lt;li&gt;Thoroughly tested and documented over years of development.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;👎 Cons&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires a separate test runner like &lt;a href="https://karma-runner.github.io/latest/index.html" rel="noopener noreferrer"&gt;Karma&lt;/a&gt; or &lt;a href="https://www.npmjs.com/package/jasmine" rel="noopener noreferrer"&gt;Jasmine CLI&lt;/a&gt; for executing tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Node.js Unit Testing: Best Practices
&lt;/h2&gt;

&lt;p&gt;Here's a list of best practices to build robust Node.js unit tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep unit tests small and focused on single functions to make them easier to understand and maintain.&lt;/li&gt;
&lt;li&gt;Avoid declaring more than one assertion in the same test.&lt;/li&gt;
&lt;li&gt;Use descriptive test names that clearly indicate what is being tested and the expected outcome.&lt;/li&gt;
&lt;li&gt;Rely on mocks or stubs to isolate the code that's being tested from external dependencies such as databases, APIs, or other modules.&lt;/li&gt;
&lt;li&gt;Use helper functions, setup functions, and utilities to avoid duplicating test code.&lt;/li&gt;
&lt;li&gt;Integrate your unit tests into your CI/CD pipeline to ensure that tests run automatically at each deployment.&lt;/li&gt;
&lt;li&gt;Adopt code coverage tools to monitor test coverage and identify areas of the codebase that are not adequately tested.&lt;/li&gt;
&lt;li&gt;Write clear and concise documentation for your tests, including information about what is being tested, why it's important, and any relevant context or assumptions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this blog post, we explored Node.js unit testing and its benefits for backend projects.&lt;/p&gt;

&lt;p&gt;You now know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The definition of unit tests.&lt;/li&gt;
&lt;li&gt;What elements a Node.js unit test consist of.&lt;/li&gt;
&lt;li&gt;The most popular unit testing libraries and frameworks for Node.js.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you liked this post, &lt;a href="https://blog.appsignal.com/javascript-sorcery" rel="noopener noreferrer"&gt;subscribe to our JavaScript Sorcery list&lt;/a&gt; for a monthly deep dive into more magical JavaScript tips and tricks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.P.S. If you need an APM for your Node.js app, go and &lt;a href="https://www.appsignal.com/nodejs" rel="noopener noreferrer"&gt;check out the AppSignal APM for Node.js&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>node</category>
    </item>
    <item>
      <title>How to Define a Counter in MySQL</title>
      <dc:creator>Antonello Zanini</dc:creator>
      <pubDate>Fri, 09 Aug 2024 13:40:45 +0000</pubDate>
      <link>https://dev.to/antozanini/how-to-define-a-counter-in-mysql-1jfo</link>
      <guid>https://dev.to/antozanini/how-to-define-a-counter-in-mysql-1jfo</guid>
      <description>&lt;p&gt;The simplest way to identify an object in a database is to assign it a unique integer, as it happens with order numbers. Not surprisingly, most databases support the definition of auto-incremental values. Essentially, an incremental value is just a counter used to uniquely identify an entry in a table. Well, there are several ways to define a counter in MySQL!&lt;/p&gt;

&lt;p&gt;In this article, you will understand what a counter is, where it is useful in a database, and how to implement it in MySQL.&lt;/p&gt;

&lt;p&gt;Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Counter?
&lt;/h2&gt;

&lt;p&gt;In programming, a counter is a variable used to keep track of the number of occurrences of certain events or actions. In most cases, it is used in a loop to increment a numerical value automatically and keep track of the number of iterations. This is why counters are generally associated with the concept of auto-incremental numbers.&lt;/p&gt;

&lt;p&gt;In databases, a counter is commonly used to generate unique identifiers for records, such as sequential numbers for primary keys or tracking numbers for events.&lt;/p&gt;

&lt;p&gt;MySQL provides the &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/example-auto-increment.html" rel="noopener noreferrer"&gt;&lt;code&gt;AUTO_INCREMENT&lt;/code&gt;&lt;/a&gt; attribute to automatically increase a column value by one with each new record. However, this attribute only increments values by one unit at a time. For more customized behavior, you might need to manually define a MySQL counter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reasons to Use a Counter in MySQL
&lt;/h2&gt;

&lt;p&gt;These are the top 5 reasons why using a counter in MySQL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create unique identifiers&lt;/strong&gt;: Sequential values are ideal for primary keys to ensure &lt;a href="https://www.dbvis.com/thetable/understanding-postgresql-data-integrity/" rel="noopener noreferrer"&gt;data integrity&lt;/a&gt; and consistency.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhanced data management:&lt;/strong&gt; Automatically incremented counters help to sort and identify records.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplified data entry&lt;/strong&gt;: Automatic unique ID generation reduces manual entry errors and simplifies the data insertion process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Efficient record tracking:&lt;/strong&gt; Counters make it easier to track and manage records. That is particularly true in applications where sequential or unique numbering is critical, such as order processing or inventory management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Customizable formats:&lt;/strong&gt; By using counters with custom formats, you can create meaningful identifiers that provide context and organization to your data.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Defining a Counter in MySQL
&lt;/h2&gt;

&lt;p&gt;Explore the two most common approaches to defining counters in MySQL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The MySQL sample queries below will be executed in &lt;a href="https://www.dbvis.com/" rel="noopener noreferrer"&gt;DbVisualizer&lt;/a&gt;, the database client with the highest user satisfaction in the market. Keep in mind that you can run them in any other SQL client.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using AUTO_INCREMENT
&lt;/h3&gt;

&lt;p&gt;By marking a column with the &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; attribute, MySQL will automatically give it an incremental value when inserting new records. This ensures that each entry has a unique, sequential identifier.&lt;/p&gt;

&lt;p&gt;Consider the following example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;surname&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;role&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above query creates an &lt;code&gt;employees&lt;/code&gt; table with an &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; primary key &lt;code&gt;id&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, suppose the &lt;code&gt;employees&lt;/code&gt; table already contains these 5 records:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7cw21nkf4u6t9ppufm6u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7cw21nkf4u6t9ppufm6u.png" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To add three more records, use the following &lt;a href="https://www.dbvis.com/thetable/crud-advanced-insert-queries/" rel="noopener noreferrer"&gt;&lt;code&gt;INSERT&lt;/code&gt;&lt;/a&gt; query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;surname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Frank'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Miller'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'frank.miller@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Developer'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Grace'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Davis'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'grace.davis@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Manager'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Hank'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Wilson'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'hank.wilson@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Designer'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;employees&lt;/code&gt; will now contain:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvs98qh725c17ncg1n94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvvs98qh725c17ncg1n94.png" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that the &lt;code&gt;id&lt;/code&gt; column of the new records has been populated with an incremental value by default. In particular, you can omit the &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; column in the &lt;code&gt;INSERT&lt;/code&gt; statement (or set it to &lt;code&gt;NULL&lt;/code&gt;), as MySQL will populate it for you.&lt;/p&gt;

&lt;p&gt;Note that the &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; attribute only works on integer &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/constraint-primary-key.html" rel="noopener noreferrer"&gt;&lt;code&gt;PRIMARY KEY&lt;/code&gt;&lt;/a&gt;s. Additionally, &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; values are always incremented by one unit at a time. Check out this guide to find out more about &lt;a href="https://www.dbvis.com/thetable/working-with-numeric-data-types-in-mysql-a-comprehensive-guide/" rel="noopener noreferrer"&gt;numeric data types in MySQL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To customize the behavior of &lt;code&gt;AUTO_INCREMENT&lt;/code&gt;, you can use the following variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.mysql.com/doc/refman/8.4/en/replication-options-source.html#sysvar_auto_increment_increment" rel="noopener noreferrer"&gt;&lt;code&gt;auto_increment_increment&lt;/code&gt;&lt;/a&gt;: Defines the increment step for the &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; values. The default value is &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.mysql.com/doc/refman/8.4/en/replication-options-source.html#sysvar_auto_increment_offset" rel="noopener noreferrer"&gt;&lt;code&gt;auto_increment_offset&lt;/code&gt;&lt;/a&gt;: Sets the starting point for the &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; values. For example, setting it to &lt;code&gt;5&lt;/code&gt; will make the first &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; value start from &lt;code&gt;5&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the same time, these variables apply to all &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; columns in the database and have either global or session scope. In other words, they cannot be applied to individual tables.&lt;/p&gt;

&lt;p&gt;The following approaches will give you more flexibility when defining counters in MySQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using a Variable
&lt;/h3&gt;

&lt;p&gt;A simple approach to creating a custom counter in MySQL is to use a &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/user-variables.html" rel="noopener noreferrer"&gt;user-defined variable&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, suppose you want each employee to have an internal auto-incremental ID in the following format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Roogler#&amp;lt;incremental_number&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add an &lt;code&gt;internal_id&lt;/code&gt; column to &lt;code&gt;employees&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;internal_id&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you can achieve the desired result with the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- initialize the counter&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- update the internal_id column with the formatted incremental values&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;internal_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Roogler#'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses a variable to implement the counter and the &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/string-functions.html#function_concat" rel="noopener noreferrer"&gt;&lt;code&gt;CONCAT&lt;/code&gt;&lt;/a&gt; function to produce the internal ID in the desired format.&lt;/p&gt;

&lt;p&gt;Note that the starting value and the way you increment the counter are totally customizable. This time, there are no restrictions.&lt;/p&gt;

&lt;p&gt;Execute the query in your MySQL database client:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F07cddwia02h0v7pn8tus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F07cddwia02h0v7pn8tus.png" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that DbVisualizer comes with &lt;a href="https://www.dbvis.com/database/mysql/" rel="noopener noreferrer"&gt;full support from MySQL variables&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you inspect the data in the &lt;code&gt;employees&lt;/code&gt; table, you will now see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq87alsyxxqdc9psoz293.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq87alsyxxqdc9psoz293.png" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wonderful! Mission complete.&lt;/p&gt;

&lt;p&gt;The main drawback of this solution is that user-defined variables in MySQL are session-specific. This means that their values are only retained for the duration of the current session. So, they are not persistent across different sessions or connections.&lt;/p&gt;

&lt;p&gt;For a more persistent solution, you could use a stored procedure along with an &lt;a href="https://www.dbvis.com/thetable/sql-triggers-what-they-are-and-how-to-use-them/" rel="noopener noreferrer"&gt;SQL trigger&lt;/a&gt; to automatically update the &lt;code&gt;internal_id&lt;/code&gt; every time a new employee is inserted. For detailed instructions, see this article on &lt;a href="https://www.dbvis.com/thetable/stored-procedures-in-sql-a-complete-tutorial/" rel="noopener noreferrer"&gt;how to use stored procedures in SQL&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this guide, you saw what a counter is, why it is useful, and how to implement it in MySQL. You now know that MySQL provides the &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; keyword to define incremental integer primary keys and also supports custom counter definition.&lt;/p&gt;

&lt;p&gt;As learned here, dealing with auto-incremental values becomes easier with a powerful client tool like DbVisualizer. This comprehensive database client supports several DBMS technologies, has advanced query optimization capabilities, and can generate ERD-type schemas with a single click. &lt;a href="https://www.dbvis.com/download/" rel="noopener noreferrer"&gt;Try DbVisualizer for free!&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How to count records in MySQL?
&lt;/h3&gt;

&lt;p&gt;To count records in MySQL, use the &lt;a href="https://dev.mysql.com/doc/refman/8.4/en/aggregate-functions.html#function_count" rel="noopener noreferrer"&gt;&lt;code&gt;COUNT&lt;/code&gt;&lt;/a&gt; aggregate function as in the sample query below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns the total number of rows in the specified table. When applied to a column, &lt;code&gt;COUNT(column_name)&lt;/code&gt; counts all non-&lt;code&gt;NULL&lt;/code&gt; values in the specified column.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between COUNT and a counter in MySQL?
&lt;/h3&gt;

&lt;p&gt;In MySQL, &lt;code&gt;COUNT&lt;/code&gt; is an aggregate function to calculate the number of rows in a result set. Instead, a counter is a mechanism used to generate sequential numbers. &lt;code&gt;COUNT&lt;/code&gt; is used for aggregation and reporting, whereas a counter is employed to assign a unique identifier or track the order of records as they are inserted into a table.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use AUTO_INCREMENT or define a custom counter in MySQL?
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;AUTO_INCREMENT&lt;/code&gt; when you need an automatic way to generate sequential IDs for a primary key. On the other hand, if you require custom behavior—like specific formatting, starting values, or incrementing patterns—a custom counter implemented using a variable might be more appropriate.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to define a variable in MySQL?
&lt;/h3&gt;

&lt;p&gt;To define a variable in MySQL in a session, you must use the &lt;code&gt;SET&lt;/code&gt; statement as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;variable_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, &lt;code&gt;@variable_name&lt;/code&gt; is the variable and &lt;code&gt;value&lt;/code&gt; is its initial value. This variable can be used within the session for calculations, conditions, or as part of queries. For local variables within stored procedures or functions, you need instead the &lt;code&gt;DECLARE&lt;/code&gt; statement followed by &lt;code&gt;SET&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DECLARE&lt;/span&gt; &lt;span class="n"&gt;variable_name&lt;/span&gt; &lt;span class="n"&gt;datatype&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;variable_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Does DbVisualizer support database variables?
&lt;/h3&gt;

&lt;p&gt;Yes, DbVisualizer natively supports more than 50 database technologies, with full support for over 30 of them. The full support includes database variables and many other features. Check out the list of supported &lt;a href="https://www.dbvis.com/supported-databases/" rel="noopener noreferrer"&gt;databases&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The post "&lt;a href="https://writech.run/blog/how-to-define-a-counter-in-mysql/" rel="noopener noreferrer"&gt;How to Define a Counter in MySQL&lt;/a&gt;" appeared first on &lt;a href="https://writech.run" rel="noopener noreferrer"&gt;Writech&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>mysql</category>
      <category>programming</category>
      <category>sql</category>
    </item>
    <item>
      <title>How to Perform Data Validation in Node.js</title>
      <dc:creator>Antonello Zanini</dc:creator>
      <pubDate>Wed, 03 Jul 2024 15:02:41 +0000</pubDate>
      <link>https://dev.to/appsignal/how-to-perform-data-validation-in-nodejs-1cj1</link>
      <guid>https://dev.to/appsignal/how-to-perform-data-validation-in-nodejs-1cj1</guid>
      <description>&lt;p&gt;Data validation is essential to avoid unexpected behavior, prevent errors, and improve security. It can be performed both on a web page — where data is entered — and on the server, where the data is processed.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll explore data validation in the Node.js backend. Then, you'll learn how to implement it in Express using the &lt;code&gt;express-validator&lt;/code&gt; library.&lt;/p&gt;

&lt;p&gt;Get ready to become a Node.js data validation expert!&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Data Validation?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Data_validation" rel="noopener noreferrer"&gt;Data validation&lt;/a&gt; ensures that data (whether entered or provided) is correct, consistent, and useful for its intended purpose. This process is typically performed in frontend applications, such as when dealing with forms.&lt;/p&gt;

&lt;p&gt;Likewise, it is essential to validate data in the backend as well. In this case, data validation involves checking path and query parameters, as well as the body data sent to servers. This ensures that the data received by each API meets the specified criteria, preventing errors and vulnerabilities and ensuring the smooth functionality of your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Main Benefits of Validating Incoming Requests in Node.js
&lt;/h2&gt;

&lt;p&gt;Validating incoming requests in Node.js offers several key benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Enhancing security&lt;/strong&gt;: Mitigate some threats, such as injection attacks and data breaches. Proper validation prevents attackers from exploiting vulnerabilities in your application by sending it malformed or malicious data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improving reliability&lt;/strong&gt;: Ensure that only valid and sanitized data is processed and stored in the backend application. That enhances the overall integrity and reliability of the data, leading to a more robust and trustworthy server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintaining compliance&lt;/strong&gt;: Make sure that the data handled by the server adheres to specific data format requirements or meets internal coding standards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that you know what data validation is and why you should enforce it in your Node.js application, let's see how to do it in this step-by-step tutorial!&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow this tutorial, you need a Node.js 18+ application with a few endpoints. For example, the following Express server is perfect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// initialize an Express server&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="c1"&gt;// an array to use as an in-memory database&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;john.doe@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;John Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jane.smith@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Jane Smith&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob.johnson@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bob Johnson&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice.williams@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alice Williams&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mike.brown@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mike Brown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sara.taylor@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sara Taylor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chris.lee@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Chris Lee&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;emily.davis@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Emily Davis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alex.johnson@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alex Johnson&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lisa.wilson@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lisa Wilson&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// define three sample endpoints&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/users/:userId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// find a user by id&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User not found!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// select all users by default&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filteredUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// filter users by fullName with a case-insensitive search&lt;/span&gt;
    &lt;span class="nx"&gt;filteredUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filteredUsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// add a new user with an auto-incremented id&lt;/span&gt;
  &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;maxId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;newUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// start the server locally on port 3000&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server listening at http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defines a local variable named &lt;code&gt;users&lt;/code&gt; as an in-memory database. Then, it initializes an Express application with the following three endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /api/v1/users/:userId&lt;/code&gt;: To retrieve a single user from the &lt;code&gt;users&lt;/code&gt; array based on its id.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /api/v1/users&lt;/code&gt;: To get the list of users in the database. It accepts an optional &lt;code&gt;search&lt;/code&gt; query parameter to filter users by their full name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /api/v1/users&lt;/code&gt;: To add a new user to the &lt;code&gt;users&lt;/code&gt; array.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, you'll need a library to perform data validation in Node.js.&lt;br&gt;
With thousands of weekly downloads, &lt;a href="https://github.com/express-validator/express-validator" rel="noopener noreferrer"&gt;&lt;code&gt;express-validator&lt;/code&gt;&lt;/a&gt; is the most popular option.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;express-validator&lt;/code&gt; provides a set of Express middleware to validate and sanitize incoming data to server APIs. Behind the scenes, these middleware functions are powered by &lt;a href="https://github.com/validatorjs/validator.js" rel="noopener noreferrer"&gt;&lt;code&gt;validator.js&lt;/code&gt;&lt;/a&gt;. If you are unfamiliar with this package, &lt;code&gt;validator.js&lt;/code&gt; is the most widely used data validation library in the entire JavaScript ecosystem.&lt;/p&gt;

&lt;p&gt;What makes &lt;code&gt;express-validator&lt;/code&gt; so successful is its rich set of features and intuitive syntax for validating Express endpoints.&lt;br&gt;
It also provides tools for determining whether a request is valid, functions for accessing sanitized data, and more.&lt;/p&gt;

&lt;p&gt;Add the &lt;a href="https://www.npmjs.com/package/express-validator" rel="noopener noreferrer"&gt;&lt;code&gt;express-validator&lt;/code&gt; npm package&lt;/a&gt; to your project's dependencies with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;express-validator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect! You now have everything you need to perform data validation in an Express application.&lt;/p&gt;

&lt;p&gt;For a faster setup, clone the &lt;a href="https://github.com/Tonel/nodejs-express-validator-demo" rel="noopener noreferrer"&gt;GitHub repository supporting this guide&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Tonel/nodejs-express-validator-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll find the Express server above and further implementations in dedicated branches.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;express-validator&lt;/code&gt; supports two equivalent ways to implement data validation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://express-validator.github.io/docs/guides/validation-chain" rel="noopener noreferrer"&gt;&lt;strong&gt;Validation Chain&lt;/strong&gt;&lt;/a&gt;: Define the validation rules by calling one method after another through &lt;a href="https://en.wikipedia.org/wiki/Method_chaining" rel="noopener noreferrer"&gt;method chaining&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://express-validator.github.io/docs/guides/schema-validation" rel="noopener noreferrer"&gt;&lt;strong&gt;Schema Validation&lt;/strong&gt;&lt;/a&gt;: Define the validation rules in an object-based schema to match against the incoming data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's dive into both!&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Validation in Node.js With Validation Chains
&lt;/h2&gt;

&lt;p&gt;Learn how to implement data validation through validation chains in &lt;code&gt;express-validator&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understand Validator Chains
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;express-validator&lt;/code&gt;, validation chains always begin with one of the following middleware functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://express-validator.github.io/docs/api/check/" rel="noopener noreferrer"&gt;&lt;code&gt;check()&lt;/code&gt;&lt;/a&gt;: Creates a validation chain for the selected fields in any of the &lt;code&gt;req.body&lt;/code&gt;, &lt;code&gt;req.cookies&lt;/code&gt;, &lt;code&gt;req.headers&lt;/code&gt;, &lt;code&gt;req.query&lt;/code&gt;, or &lt;code&gt;req.params&lt;/code&gt; locations. If the specified fields are present in more than one location, the validation chain processes all instances of that field's value.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://express-validator.github.io/docs/api/check/#body" rel="noopener noreferrer"&gt;&lt;code&gt;body()&lt;/code&gt;&lt;/a&gt;: Same as &lt;code&gt;check()&lt;/code&gt;, but it only checks fields in &lt;code&gt;req.body&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://express-validator.github.io/docs/api/check/#cookie" rel="noopener noreferrer"&gt;&lt;code&gt;cookie()&lt;/code&gt;&lt;/a&gt;: Same as &lt;code&gt;check()&lt;/code&gt;, but it only checks fields in &lt;code&gt;req.cookies&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://express-validator.github.io/docs/api/check/#header" rel="noopener noreferrer"&gt;&lt;code&gt;header()&lt;/code&gt;&lt;/a&gt;: Same as &lt;code&gt;check()&lt;/code&gt;, but it only checks fields in &lt;code&gt;req.headers&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://express-validator.github.io/docs/api/check/#param" rel="noopener noreferrer"&gt;&lt;code&gt;param()&lt;/code&gt;&lt;/a&gt;: Same as &lt;code&gt;check()&lt;/code&gt;, but it only checks fields in &lt;code&gt;req.params&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://express-validator.github.io/docs/api/check/#query" rel="noopener noreferrer"&gt;&lt;code&gt;query()&lt;/code&gt;&lt;/a&gt;: Same as &lt;code&gt;check()&lt;/code&gt;, but it only checks fields in &lt;code&gt;req.query&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These middleware functions accept one or more field names to select from incoming data. They also provide some methods, which is possible because &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions" rel="noopener noreferrer"&gt;JavaScript functions are actually first-class objects&lt;/a&gt;. Their methods always return themselves, leading to the method chaining pattern.&lt;/p&gt;

&lt;p&gt;So, let's assume that you want to ensure that the &lt;code&gt;name&lt;/code&gt; body parameter contains at least 4 characters when it is present.&lt;br&gt;
This is how you can specify that with an &lt;code&gt;express-validator&lt;/code&gt; validator chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isLength&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each method chain will return a valid Express middleware function you can use for validation in a route handler.&lt;br&gt;
A single route handler can have one or more validation middleware, each referring to different data fields.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://express-validator.github.io/docs/api/validation-chain/" rel="noopener noreferrer"&gt;Check out the express-validator documentation for all validation chain methods&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Time to see validation chains in action!&lt;/p&gt;
&lt;h3&gt;
  
  
  Validate Route Parameters
&lt;/h3&gt;

&lt;p&gt;Suppose you want to ensure that the &lt;code&gt;userId&lt;/code&gt; parameter in &lt;code&gt;GET /api/v1/users/:userId&lt;/code&gt; is an integer. This is what you may end up writing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/users/:userId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isInt&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// business logic...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the validation chain defined above is correct, it's not enough, as &lt;code&gt;express-validator&lt;/code&gt; doesn't report validation errors to users automatically. Why? Because it's better if developers always manually define how to handle invalid data!&lt;/p&gt;

&lt;p&gt;You can access the result object of data validation in an Express endpoint through the &lt;a href="https://express-validator.github.io/docs/api/validation-result/#validationresult" rel="noopener noreferrer"&gt;&lt;code&gt;validationResult()&lt;/code&gt;&lt;/a&gt; function.&lt;/p&gt;

&lt;p&gt;Import it along with the validation middleware function from &lt;code&gt;express-validator&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="nx"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express-validator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, you can define the validated route handler for &lt;code&gt;GET /api/v1/users/:userId&lt;/code&gt; as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/users/:userId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isInt&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// extract the data validation result&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// find a user by id&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User not found!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;userId&lt;/code&gt; is not an integer, the endpoint will return a &lt;code&gt;400&lt;/code&gt; response with validation error messages. In production, you should override that response with a generic message to avoid providing useful information to potential attackers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validate Query Parameters
&lt;/h3&gt;

&lt;p&gt;In this case, you want to ensure that the &lt;code&gt;search&lt;/code&gt; query parameter in the &lt;code&gt;GET /api/v1/users&lt;/code&gt; endpoint is not empty or blank when it is present.&lt;/p&gt;

&lt;p&gt;This is the validation chain you need to define:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;search&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;notEmpty&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the order of the method calls in the chain is important. If you switch &lt;code&gt;trim()&lt;/code&gt; with &lt;code&gt;notEmpty()&lt;/code&gt;, blank strings will pass the validation rule.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;trim()&lt;/code&gt; is a sanitizer method and it affects the values stored in &lt;code&gt;search&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you need to access the sanitized data directly in the route handler, you can call the &lt;a href="https://express-validator.github.io/docs/api/matched-data" rel="noopener noreferrer"&gt;&lt;code&gt;matchedData()&lt;/code&gt;&lt;/a&gt; function. The validated and sanitized endpoint will now be specified as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;search&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;notEmpty&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// extract the data validation result&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// select all users by default&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filteredUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// read the matched query data from "req"&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;matchedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// filter users by fullName with a case-insensitive search&lt;/span&gt;
        &lt;span class="nx"&gt;filteredUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filteredUsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that &lt;code&gt;req.query.search&lt;/code&gt; will contain the original value of the &lt;code&gt;search&lt;/code&gt; query parameter, but you want to use its sanitized value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validate Body Data
&lt;/h3&gt;

&lt;p&gt;Now, suppose you want the body of &lt;code&gt;POST /api/v1/users&lt;/code&gt; to follow these rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fullName&lt;/code&gt; must not be empty or blank.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;email&lt;/code&gt; must be a valid email. If there's an invalid email, the validation error message should be “Not a valid e-mail address.”&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;age&lt;/code&gt; must be an integer greater than or equal to 18.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how you can implement the desired data validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fullName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;notEmpty&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isEmail&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;withMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not a valid e-mail address&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;age&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;isInt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// extract the data validation result&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// read the matched body data from "req"&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;matchedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// add a new user with an auto-incremented id&lt;/span&gt;
      &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;maxId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;newUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a couple of critical aspects to emphasize in this example. First, a single route handler can have multiple validation middlewares referring to the same data source. Second, the methods offered by the middleware functions not only specify how to validate the data, but also allow you to customize error messages and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test the Validated Endpoints
&lt;/h3&gt;

&lt;p&gt;Launch your Node.js application and verify that the data validation logic works as expected. Otherwise, check out the &lt;code&gt;chain-validation&lt;/code&gt; branch from the &lt;a href="https://github.com/Tonel/nodejs-express-validator-demo" rel="noopener noreferrer"&gt;repository that supports this article&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout chain-validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the project dependencies and launch an Express development server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your Node.js application should now be listening locally on port 3000. Open your favorite HTTP client and try to make a &lt;code&gt;GET&lt;/code&gt; request to &lt;code&gt;/api/v1/users/edw&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37i64vd7hp08c4tq0z3u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37i64vd7hp08c4tq0z3u.png" alt="Make GET request" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since &lt;code&gt;edw&lt;/code&gt; is not a number, you'll get a &lt;code&gt;400&lt;/code&gt; response.&lt;br&gt;
Specifically, the error array generated by &lt;code&gt;express-validator&lt;/code&gt; contains one or more error objects in the following format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;field&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;edw&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;msg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;location&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;params&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can simulate another validation error by calling the &lt;code&gt;GET /api/v1/users&lt;/code&gt; endpoint with a blank &lt;code&gt;search&lt;/code&gt; query parameter:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fglbw0d9gwogqqfev54cb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fglbw0d9gwogqqfev54cb.png" alt="Call GET endpoint" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, trigger a validation error by calling the &lt;code&gt;POST /api/v1/users&lt;/code&gt; API with an invalid body:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gfvxzwh8qfhp2lc84ce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5gfvxzwh8qfhp2lc84ce.png" alt="Validation error" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you call the three endpoints with the expected data instead, they will return a successful response as expected.&lt;/p&gt;

&lt;p&gt;Great, you just learned how to perform data validation in Node.js! All that remains is to explore the equivalent schema-based approach to data validation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Validation in Node.js with Schema Validation
&lt;/h2&gt;

&lt;p&gt;Let's now see how to define data validation through schema objects with &lt;code&gt;express-validator&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understand Schema Validation
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;express-validator&lt;/code&gt;, &lt;a href="https://express-validator.github.io/docs/guides/schema-validation#what-are-schemas" rel="noopener noreferrer"&gt;schemas&lt;/a&gt; are an object-based way of defining validation and/or sanitization rules on a request.&lt;/p&gt;

&lt;p&gt;While their syntax differs from validation chains, they offer exactly the same functionality. Under the hood, &lt;code&gt;express-validator&lt;/code&gt; translates schemas into validation chain functions.&lt;br&gt;
Thus, you can choose between one syntax or the other, depending on your preference.&lt;br&gt;
The same Express application can have some endpoints validated with chains and others validated with schemas.&lt;/p&gt;

&lt;p&gt;Schemas are simple JavaScript objects whose keys represent the fields to validate. Schema values contain validation rules in the form of objects.&lt;br&gt;
Pass a schema object to the &lt;a href="https://express-validator.github.io/docs/api/check-schema" rel="noopener noreferrer"&gt;&lt;code&gt;checkSchema()&lt;/code&gt;&lt;/a&gt; function to get an Express validation middleware.&lt;/p&gt;

&lt;p&gt;For example, this is how you can use a schema to ensure that the &lt;code&gt;name&lt;/code&gt; body parameter contains at least 4 characters when it is present:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;checkSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;isLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, &lt;code&gt;checkSchema()&lt;/code&gt; behaves like &lt;code&gt;check()&lt;/code&gt;. To specify which input data sources it should check, you can pass them in an array as the second parameter. In the example above, the validation schema object will only be applied to the body data.&lt;/p&gt;

&lt;p&gt;Sometimes, you may need to validate a body's inner field. &lt;a href="https://express-validator.github.io/docs/guides/field-selection" rel="noopener noreferrer"&gt;Check out the documentation to see what &lt;code&gt;express-validator&lt;/code&gt; offers in field selection&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validate Route, Query, and Body Data With Schemas
&lt;/h3&gt;

&lt;p&gt;Here's how you can specify the validation rules shown in the method chaining section above through schema validation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /api/v1/users/:userId&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/users/:userId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;checkSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;isInt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;params&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// extract the data validation result&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="c1"&gt;// find a user by id&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User not found!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /api/v1/users&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;checkSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;notEmpty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// extract the data validation result&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// select all users by default&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;filteredUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// read the matched query data from "req"&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;matchedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// filter users by fullName with a case-insensitive search&lt;/span&gt;
        &lt;span class="nx"&gt;filteredUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filteredUsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the order of the attributes in the schema object matters.&lt;br&gt;
Placing the &lt;code&gt;trim&lt;/code&gt; attribute after &lt;code&gt;notEmpty&lt;/code&gt; will result in a different validation rule.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /api/v1/users&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;checkSchema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;notEmpty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Not a valid e-mail address&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;isEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;isInt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// extract the data validation result&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// read the body data from the matched data&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;matchedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// add a new user with an auto-incremented id&lt;/span&gt;
      &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;maxId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;newUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;As you can see, not much changes from chain validation. The two approaches are completely equivalent.&lt;/p&gt;
&lt;h3&gt;
  
  
  Test the Validated Endpoints
&lt;/h3&gt;

&lt;p&gt;Start your Express application and prepare to test your schema-based data validation.&lt;br&gt;
Otherwise, check out the &lt;code&gt;schema-validation&lt;/code&gt; branch from the &lt;a href="https://github.com/Tonel/nodejs-express-validator-demo" rel="noopener noreferrer"&gt;repository supporting this article&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout chain-validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the project's dependencies, start the local server, and repeat the API calls made in the method chaining validation section.&lt;br&gt;
You should get the exact same results!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up: Protect Your Node.js Server From Unexpected Incoming Data
&lt;/h2&gt;

&lt;p&gt;In this post, we defined Node.js data validation and explored its benefits for a backend application.&lt;/p&gt;

&lt;p&gt;You now know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The definition of data validation&lt;/li&gt;
&lt;li&gt;Why you should check the data received by an endpoint before feeding it to business logic&lt;/li&gt;
&lt;li&gt;How to implement data validation in Node.js with two different approaches: method chaining and schema validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. If you liked this post, &lt;a href="https://blog.appsignal.com/javascript-sorcery" rel="noopener noreferrer"&gt;subscribe to our JavaScript Sorcery list&lt;/a&gt; for a monthly deep dive into more magical JavaScript tips and tricks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.P.S. If you need an APM for your Node.js app, go and &lt;a href="https://www.appsignal.com/nodejs" rel="noopener noreferrer"&gt;check out the AppSignal APM for Node.js&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>node</category>
    </item>
    <item>
      <title>Best PostgreSQL GUI Tools</title>
      <dc:creator>Antonello Zanini</dc:creator>
      <pubDate>Sat, 15 Jun 2024 14:26:11 +0000</pubDate>
      <link>https://dev.to/antozanini/best-postgresql-gui-tools-406m</link>
      <guid>https://dev.to/antozanini/best-postgresql-gui-tools-406m</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; There are many database GUI tools available, but only a few of them have been designed specifically for PostgreSQL. If you want to manage your PostgreSQL databases effortlessly, you must adopt an advanced PostgreSQL GUI tool. Here, you can find a list of the four best PostgreSQL applications for you.&lt;/p&gt;

&lt;p&gt;When the first version of PostgreSQL was released, the only way to access data was to run queries from the command line. As you can imagine, interacting with a database via the command line is something that only skilled users can do. This is because you need to know the PostgreSQL language in depth.&lt;/p&gt;

&lt;p&gt;Over time, the needs of end users have evolved for developers, data scientists, and data analysts alike. As a result, several PostgreSQL GUI tools have entered the market, and you can now choose between many PostgreSQL clients.&lt;/p&gt;

&lt;p&gt;If you've ever felt limited by your PostgreSQL GUI tool or never used one, this article might shed some light on which one to choose. This article will dig into the four best PostgreSQL GUI tools for developers. But first, let's take a look at why you should be using one and which criteria were considered while making the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why You Need a PostgreSQL GUI Tool
&lt;/h2&gt;

&lt;p&gt;Adopting a PostgreSQL GUI tool can bring many advantages to your data management process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visual feedback:&lt;/strong&gt; Managing data visually in the tool makes everything easier and opens the databases to non-technical members of your team.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Better data management:&lt;/strong&gt; A GUI tool specifically designed to handle PostgreSQL databases makes data management easier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; You can use the same tools to connect to several PostgreSQL databases and manage them all.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Elements of a Good PostgreSQL GUI Tool
&lt;/h2&gt;

&lt;p&gt;There are several aspects to consider when selecting and evaluating PostgreSQL GUI tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Most commonly used:&lt;/strong&gt; The more people who adopt the tool, the more documentation there will be online. After all, it's easier to get support if a tool is backed by a large community. This is more likely to happen if the tool is available on all major operating systems.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easy to use and learn:&lt;/strong&gt; PostgreSQL is a complex technology, and this is why a GUI tool should make everything easier. A good PostgreSQL GUI tool should be easy to learn and should not require several hours of training.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugin support:&lt;/strong&gt; The ability to extend the PostgreSQL GUI tool with plugins developed by the community makes it future-ready, as new features can be introduced into the tool at any time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Customizable user interface:&lt;/strong&gt; You should be able to customize the tool to fit your particular needs. The more customizable the user interface of a PostgreSQL GUI tool is, the better the resulting user experience will be.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fast and reliable:&lt;/strong&gt; The tool should be free of major bugs and ensure good performance without using too many resources. Non-technical team members may not be able to run a PostgreSQL GUI tool that requires several GB of RAM because they typically don't have hardware as powerful as technical team members.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Top Four PostgreSQL GUI Tools
&lt;/h2&gt;

&lt;p&gt;Here is a list of four PostgreSQL GUI tools that meet the criteria presented earlier, in alphabetical order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;DbVisualizer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;OmniDB&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;pgAdmin&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TablePlus&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  DbVisualizer
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fex3k2fcv4bp2k24bivea.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fex3k2fcv4bp2k24bivea.png" alt="DbVisualizer in action" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.dbvis.com/" rel="noopener noreferrer"&gt;DbVisualizer&lt;/a&gt; is one of the most reliable database GUI tools on the market. The first version was released in 1999, and it now supports all major databases, including PostgreSQL. DbVisualizer is the database GUI tool with the &lt;a href="https://www.g2.com/categories/database-management-systems-dbms?tab=highest_rated" rel="noopener noreferrer"&gt;highest user satisfaction on G2&lt;/a&gt; and has even been adopted by NASA.&lt;/p&gt;

&lt;p&gt;DbVisualizer can be installed on Windows, macOS, and Linux; all it needs to run is Java. Its Java nature makes it a bit slower than other tools, but performance is not the main goal of DbVisualizer.&lt;/p&gt;

&lt;p&gt;Instead, DbVisualizer aims for completeness and reliability. It therefore provides a wide range of features for writing SQL queries, visualizing data, and designing and developing databases, tables, relations, indexes, and triggers. DbVisualizer also offers in-depth help when it comes to PostgreSQL and supports all its unique features.&lt;/p&gt;

&lt;p&gt;Even though the DbVisualizer UI may appear a bit outdated compared to the other tools, you can &lt;a href="https://confluence.dbvis.com/display/UG120/Changing+the+GUI+Appearance" rel="noopener noreferrer"&gt;configure it in several ways&lt;/a&gt;, like using a dark theme.&lt;/p&gt;

&lt;p&gt;As opposed to OmniDB and TablePlus, DbVisualizer is a proprietary tool that does not support plugins. On the other hand, it offers several advanced features. One of the most important ones is the ability to visually build queries, which allows even non-technical people to perform SQL queries. DbVisualizer also comes with a query optimization feature that explains to you how and why you can improve your SQL query. It shows you how your query will be processed by the database, telling you whether or not an index will be used.&lt;/p&gt;

&lt;h3&gt;
  
  
  OmniDB
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fap3qthuca3rcl28687oq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fap3qthuca3rcl28687oq.png" alt="OmniDB in action" width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/OmniDB/OmniDB" rel="noopener noreferrer"&gt;OmniDB&lt;/a&gt; is a GUI tool that supports several databases, such as MySQL, PostgreSQL, Oracle, and MariaDB. However, its main focus is PostgreSQL. OmniDB is an open source project developed mainly by &lt;a href="https://github.com/2ndQuadrant" rel="noopener noreferrer"&gt;2ndQuadrant&lt;/a&gt;, one of the leading companies in the world when it comes to PostgreSQL.&lt;/p&gt;

&lt;p&gt;With OmniDB, you can add, edit, manage, and monitor data in a PostgreSQL database through a simple GUI interface. Even though its UI isn't fully customizable, OmniDB supports both a light and dark theme. You can also &lt;a href="https://omnidb.readthedocs.io/en/2.17.0/en/11_additional_features.html#user-settings" rel="noopener noreferrer"&gt;configure several shortcuts&lt;/a&gt; for accessing OmniDB features more easily, such as running a query.&lt;/p&gt;

&lt;p&gt;OmniDB supports Windows, Linux, and macOS and allows developers to add and share new features via plugins, so it can be extended by the community. On the other hand, the OmniDB community is still small compared to those of the other tools, which means there may be limited support for issues.&lt;/p&gt;

&lt;p&gt;Its main strength is the ability to visualize queries to help you find bottlenecks. It also comes with a smart and advanced SQL editor with autocomplete and syntax highlighting features that help you write SQL queries.&lt;/p&gt;

&lt;p&gt;At the time of writing, the latest version of OmniDB is still in beta. This means that it may have some bugs and performance issues. In other words, OmniDB may look incomplete or unreliable compared to more mature tools such as DbVisualizer.&lt;/p&gt;

&lt;h3&gt;
  
  
  pgAdmin
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8hng7r9v51f3cgjdm5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8hng7r9v51f3cgjdm5r.png" alt="pgAdmin in action" width="800" height="629"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.pgadmin.org/" rel="noopener noreferrer"&gt;pgAdmin&lt;/a&gt; is one of the most popular, most used, and most reliable PostgreSQL GUI tools available. This is because pgAdmin was developed by part of the PostgreSQL team and directly comes in the PostgreSQL installation pack. However, compared to the other tools, it is the sole GUI application that supports only PostgreSQL.&lt;/p&gt;

&lt;p&gt;pgAdmin is an open source project that supports all PostgreSQL features, from performing simple SQL queries to building complex databases. As with DbVisualizer, though, pgAdmin does not support plugins and cannot be extended by the community. At the same time, it's open source, so community extensions and plugins are not impossible.&lt;/p&gt;

&lt;p&gt;You can install pgAdmin on Windows, Linux, and macOS, and it runs as a web application that can be deployed to any server. This makes it also available from the cloud, so pgAdmin is a tool you can use anywhere.&lt;/p&gt;

&lt;p&gt;The pgAdmin user interface is simple for beginners, but it also offers several shortcuts for experienced users. This makes it an easy-to-learn yet advanced tool that can be used by any member of the team. However, the UI isn't highly customizable and might look a bit outdated when compared to TablePlus, for example.&lt;/p&gt;

&lt;h3&gt;
  
  
  TablePlus
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2f9o2gf7cdfwxvdylj6l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2f9o2gf7cdfwxvdylj6l.png" alt="TablePlus in action" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tableplus.com/" rel="noopener noreferrer"&gt;TablePlus&lt;/a&gt; is a database GUI app for relational databases. In detail, it supports MySQL, PostgreSQL, SQLite, and more. So, just like OmniDB and DbVisualizer, you can also use it for non-PostgreSQL scenarios. TablePlus has been adopted by companies such as Spotify and Intel, making it one of the more interesting PostgreSQL GUI tools on the market. This makes it a reliable tool that's directly comparable with DbVisualizer.&lt;/p&gt;

&lt;p&gt;TablePlus is a fast and native GUI tool that comes with a simple-to-use UI. You can run TablePlus almost everywhere, considering it supports iOS, macOS, and Windows. Plus, there is also an &lt;a href="https://tableplus.com/blog/2019/10/tableplus-linux-installation.html" rel="noopener noreferrer"&gt;alpha version for Linux&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The TablePlus user interface is nice and simple but also highly configurable. This makes it a versatile tool that can be easily adapted to the needs of several users. On the other hand, finding the right UI configuration for you may take a lot of time.&lt;/p&gt;

&lt;p&gt;TablePlus also supports several shortcuts for more skilled users. The tool has been developed with performance and security in mind. TablePlus is very lightweight, supports built-in SSH connections, and ensures that your credentials are stored securely. This makes it the best-performing tool of the four. Moreover, just like OmniDB, TablePlus can be easily extended by plugins developed by the community.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A PostgreSQL client is an essential tool when it comes to data management. PostgreSQL clients are particularly important because they make databases simpler for non-engineer members of your team. Data is easier to use and understand, allowing your engineers to save time while supporting the team.&lt;/p&gt;

&lt;p&gt;This article explained why you need a PostgreSQL GUI tool and how to identify a good one. You learned about four of the best PostgreSQL GUI tools on the market. TablePlus, OmniDB, and DbVisualizer support other database technologies and have excellent features to support PostgreSQL-based data management processes, while pgAdmin is the only tool officially supported by PostgreSQL developers. Keep in mind that the tool that suits you the best depends on your use cases. So, there is no real winner.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The post "&lt;a href="https://writech.run/blog/best-postgresql-gui-tools/" rel="noopener noreferrer"&gt;Best PostgreSQL GUI Tools&lt;/a&gt;" appeared first on &lt;a href="https://writech.run" rel="noopener noreferrer"&gt;Writech&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>programming</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
