<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Vitali Zaneuski on Medium]]></title>
        <description><![CDATA[Stories by Vitali Zaneuski on Medium]]></description>
        <link>https://medium.com/@vviital?source=rss-151511378409------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*GvE7l9Srsr5IVrkTyYwVVA.jpeg</url>
            <title>Stories by Vitali Zaneuski on Medium</title>
            <link>https://medium.com/@vviital?source=rss-151511378409------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 20:49:47 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@vviital/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[You don’t need a UUID anymore!]]></title>
            <link>https://medium.com/@vviital/you-dont-need-a-uuid-anymore-33cf7ab201d2?source=rss-151511378409------2</link>
            <guid isPermaLink="false">https://medium.com/p/33cf7ab201d2</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[uuid]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[Vitali Zaneuski]]></dc:creator>
            <pubDate>Tue, 25 Apr 2023 18:54:00 GMT</pubDate>
            <atom:updated>2023-04-25T18:54:42.133Z</atom:updated>
            <content:encoded><![CDATA[<p>You don’t need a UUID anymore… well, in some cases. Let’s discuss it!</p><p>There are several ways and popular packages to generate unique identifiers, and one of them is the “<a href="https://github.com/uuidjs/uuid"><strong><em>uuid</em></strong></a>” package, which has millions of downloads every day. It proposes different types of approaches for generating identifiers, ranked by version:</p><ul><li><strong><em>v1</em></strong> — creates a version 1 (timestamp) UUID.</li><li><strong><em>v3</em></strong> — creates a version 3 (namespace w/ MD5) UUID.</li><li><strong><em>v4</em></strong> — creates a version 4 (random) UUID.</li><li><strong><em>v5</em></strong> — creates a version 5 (namespace w/ SHA-1) UUID.</li></ul><p>From my experience, <strong><em>v4</em></strong> is the most commonly used UUID version when you just need a random value. Previously, to generate UUIDs in Node.js, I had to install this external package and potentially add <a href="https://www.npmjs.com/package/@types/uuid">typings</a>, as the repository is written in pure JavaScript. However, I recently discovered an interesting update in the Node.js builds. Several releases ago, the <a href="https://nodejs.org/api/crypto.html#cryptorandomuuidoptions">new method </a><a href="https://nodejs.org/api/crypto.html#cryptorandomuuidoptions">crypto.randomUUID</a> was added, which is now being used <a href="https://github.com/uuidjs/uuid/blob/main/src/v4.js">in the implementation of the “<strong><em>uuid</em></strong>” package itself</a>. Below is a digest from the repository:</p><pre>// https://github.com/uuidjs/uuid/blob/main/src/native.js<br><br>import crypto from &#39;crypto&#39;;<br><br>export default { randomUUID: crypto.randomUUID };<br><br>// https://github.com/uuidjs/uuid/blob/main/src/v4.js<br><br>import native from &#39;./native.js&#39;;<br>import rng from &#39;./rng.js&#39;;<br>import { unsafeStringify } from &#39;./stringify.js&#39;;<br><br>function v4(options, buf, offset) {<br>  if (native.randomUUID &amp;&amp; !buf &amp;&amp; !options) {<br>    return native.randomUUID();<br>  }<br><br>  options = options || {};<br><br>  const rnds = options.random || (options.rng || rng)();<br><br>  // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`<br>  rnds[6] = (rnds[6] &amp; 0x0f) | 0x40;<br>  rnds[8] = (rnds[8] &amp; 0x3f) | 0x80;<br><br>  // Copy bytes to buffer, if provided<br>  if (buf) {<br>    offset = offset || 0;<br><br>    for (let i = 0; i &lt; 16; ++i) {<br>      buf[offset + i] = rnds[i];<br>    }<br><br>    return buf;<br>  }<br><br>  return unsafeStringify(rnds);<br>}<br><br>export default v4;<br></pre><p>So, if your code runs on the server side and your Node.js distribution is version 15.6.0 or higher, you may no longer need this additional dependency and can simply use the hidden gem from the crypto module.</p><p>If you found this article helpful, please consider leaving claps or comments to encourage me to continue writing. I also welcome any feedback you may have. Thank you, and see you in the next article!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=33cf7ab201d2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Ensuring Data Security: Best Practices for Securing Zapier Triggers]]></title>
            <link>https://medium.com/@vviital/ensuring-data-security-best-practices-for-securing-zapier-triggers-de84da4de9a1?source=rss-151511378409------2</link>
            <guid isPermaLink="false">https://medium.com/p/de84da4de9a1</guid>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[integration]]></category>
            <category><![CDATA[zapier]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[security]]></category>
            <dc:creator><![CDATA[Vitali Zaneuski]]></dc:creator>
            <pubDate>Thu, 20 Apr 2023 19:34:49 GMT</pubDate>
            <atom:updated>2023-04-20T19:34:49.253Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*TW4aJ3fjWC7ZG-4F" /><figcaption>Photo by <a href="https://unsplash.com/@campaign_creators?utm_source=medium&amp;utm_medium=referral">Campaign Creators</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p><strong><em>Note</em></strong><em>: The security tips and best practices mentioned in this article are applicable to programmatically created Zapier applications. However, it’s important to note that they may not be applicable to applications created using Zapier Visual Builder, due to the limitations of this approach.</em></p><p>In this article, I will be sharing tips on enhancing the security of Zapier triggers. Protecting your workflow data from potential security risks is crucial in today’s automation-driven world. I’ll provide practical ways to improve the security of your Zapier triggers and safeguard your data’s confidentiality and integrity.</p><p>Before we discuss securing Zapier triggers, let’s first define what triggers are in the context of Zapier. <strong>Triggers</strong> serve as the initial events or actions that initiate automated workflows in Zapier. They act as a connection between different apps, allowing users to automate tasks based on specific events or conditions.</p><p>There are two types of triggers in Zapier: polling triggers and instant triggers.</p><ul><li><strong>Polling triggers</strong> involve Zapier periodically asking applications if data is ready to be consumed. The polling frequency depends on the <a href="https://zapier.com/app/pricing">Zapier plan</a>.</li><li>On the other hand, <strong>instant triggers</strong> are essentially webhooks registered on Zapier’s side, which are triggered by the system that produces data once a new piece of data is ready. The exact type and shape of the data depending on the application’s business logic.</li></ul><p>Understanding the differences between these two types of triggers is important for implementing effective applications. It’s evident that using instant triggers, whenever feasible, is advantageous in terms of user experience and system load.</p><p>Using <a href="https://github.com/zapier/zapier-platform/tree/main/packages/cli">the Zapier documentation</a>, it’s relatively straightforward to implement functional triggers and actions that align with business needs. However, one crucial aspect often overlooked is the overall security of the solution.</p><p>In summary, to ensure a fully secure solution, it’s essential to follow these best practices:</p><ul><li>Perform calls only via secure connections using HTTPS for all communication made from the Zapier application.</li><li>Implement appropriate authentication techniques, as outlined in <a href="https://platform.zapier.com/docs/auth">the Zapier documentation</a>.</li></ul><p>Although it may seem obvious and relatively easy to follow these recommendations, even after implementing them correctly, there could still be a potential vulnerability when using an instant trigger. Specifically, on the Zapier side, all content received via webhook is accepted <strong>WITHOUT</strong> verifying the sender’s identity, which leaves a potential vector of attack to start a workflow on behalf of the user.</p><p>So to address this issue, there are several options available:</p><ul><li>One possible solution is to send <strong>an authentication token</strong> to the webhook endpoint that identifies the user in some way, adding an additional layer of authentication. However, implementing this approach can be challenging as it might require mapping roles and permissions from the system to the Zapier level, which can vary across different systems and depends on each particular case;</li><li>Another option is to send a request with <strong>a minimal payload</strong>, such as an ID of a resource that should be called to retrieve the full data using auth strategy implemented by the Zapier application. This approach can be effective but may require additional implementation steps, such as allowing calls only to whitelisted resources and adding logic at the backend system to support this solution (such as temporary storage of prepared data);</li><li>The third option is to allow accepting only requests with <strong>valid digital signatures</strong>. The verification key for the signature can be securely provided using Zapier environmental variables. On the backend side, we would need to add logic to sign all incoming requests, which adds an additional layer of authentication. This approach can be effective in ensuring that only requests with valid signatures are accepted, providing an added layer of security to the system.</li></ul><p>In my experience, the third approach seems to offer the best trade-offs in terms of security and implementation effort. It can be implemented using just a few lines of code, particularly in languages like JavaScript where the crypto module can be used by both parties: Zapier application and your backend. This approach can provide robust security guarantees and can be delivered to production within a relatively short timeframe, making it a practical option for improving the security of Zapier triggers.</p><p>If you found this article helpful, please consider leaving claps or comments to encourage me to continue writing. I also welcome any feedback you may have. Thank you, and see you in the next article!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=de84da4de9a1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stop Paying for Payment Verification: How to Verify Credit Cards for Free with Stripe]]></title>
            <link>https://medium.com/@vviital/stop-paying-for-payment-verification-how-to-verify-credit-cards-for-free-with-stripe-e0637a6732bf?source=rss-151511378409------2</link>
            <guid isPermaLink="false">https://medium.com/p/e0637a6732bf</guid>
            <category><![CDATA[stripe]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[credit-cards]]></category>
            <category><![CDATA[verification]]></category>
            <category><![CDATA[development]]></category>
            <dc:creator><![CDATA[Vitali Zaneuski]]></dc:creator>
            <pubDate>Sun, 26 Mar 2023 13:12:43 GMT</pubDate>
            <atom:updated>2023-03-26T13:12:43.051Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Xv_B9F9qp9d-UJ-i" /><figcaption>Photo by <a href="https://unsplash.com/@micheile?utm_source=medium&amp;utm_medium=referral">micheile henderson</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>When implementing a subscription system in your project, it’s crucial to ensure a smooth and reliable payment process. To achieve this, many developers choose to use existing API services to process payments. However, it’s essential to verify the validity of a user’s credit card before accepting payment. This is to ensure that the card is not stolen and that it is authorized for payments.</p><p>Stripe is one such payment processing provider that offers built-in checks, such as CVC or postal code verification, to help verify credit cards. However, sometimes these checks are not sufficient. For example, if your website offers a trial period, users may need to provide their credit card information upfront, even if they haven’t yet been charged. Similarly, if a user wants to change their payment method, it’s important to verify that the new card is valid before making the switch.</p><p>To verify credit card validity, one approach is to initiate a test charge for a small amount, usually around $1, and then refund it. However, upon closer examination, this approach has several downsides, including:</p><ul><li>If the test charge takes money out of the user’s account, it’s super important to give it back right away. We need to make sure no one’s money gets stuck in our system, and that our customers don’t get mad at us. So, we gotta have a transactional flow to make sure everything runs smoothly!</li><li>Stripe imposes a fee for this kind of operation. For every dollar charged and refunded, <a href="https://weareindy.com/stripe-fee-calculator">Stripe takes a 33-cent or 33% cut out of our pocket</a>. While this may be fine for a single user, it can become a costly process for thousands of users.</li></ul><p>We seem to have a problem here and to find a solution, we first need to understand how Stripe works. Let’s start with the second problem — the fees imposed by Stripe. Stripe charges fees for finalizing operations, which means fees are applied to money transfers from Bank A to Bank B. Perhaps there’s a way to reach our goal without actually transferring any money? The answer is yes, and it’s called the “<a href="https://staxpayments.com/blog/authorize-and-capture/">Authorize and Capture</a>” approach, which breaks down the money transfer into two stages:</p><ul><li><strong>The authorization</strong> ensures the account has sufficient funds and is in good standing by holding funds as pending without charging the card.</li><li><strong>Capture</strong> is the process of transferring funds from the customer account to the merchant account, moving it from pending to complete.</li></ul><p>It turns out that we only need to perform the authorization process to verify a user’s credit card, and then roll it back to release the funds. This way, we avoid the transaction fees associated with traditional charge-and-refund methods. If the credit card has any issues, such as insufficient funds or invalid details, Stripe will respond with an error message, allowing us to provide guidance to the users. If everything is good to go, then the credit card is ready to use as the primary payment source.</p><p>Now that we understand how we can avoid costly fees while verifying credit cards with Stripe, let’s take a look at how we can implement this approach using the Stripe SDK. Essentially, the algorithm can be broken down into three simple steps:</p><ul><li>The first step of this algorithm is to <strong>create a payment intent</strong> with a value of <strong>1 dollar</strong> and set the capture_method to manual. To make the process faster and reduce the potential for points of failure, we can confirm the payment intent immediately. Depending on the circumstances, the payment intent may have a status of requires_capture if the operation is synchronous and does not require 3D security. If 3D security is required, the status will be requires_action. It’s also important to note that Stripe may respond with a business error, indicating that the payment method is not valid.</li><li><strong>Client app handling</strong> is the second step of the algorithm. If the payment method requires user action, such as 3D security, we need to notify the user and provide them with a Stripe payment intent ID and secret to finalize the intent creation. This step is optional and depends on the bank’s settings. If the payment intent has a status requires_action, the user will need to perform an action to authenticate the payment, such as entering a one-time password. Once the authentication is complete, the payment intent status will change to requires_capture, and we can move on to the next step.</li><li><strong>Stripe event handling via webhook</strong> is the final step of the algorithm. We need to set up a webhook to listen for two events: payment_intent.amount_capturable_updated and payment_intent.payment_failed. If the payment_intent.amount_capturable_updated event is triggered, and the intent has a status of requires_capture, then everything is okay, and we can cancel the intent and finalize the validation process. If the payment_intent.payment_failed event is triggered, then an error occurred, and we need to communicate it to the user.</li></ul><pre>const stripe = require(&#39;stripe&#39;)(&#39;sk_test_...&#39;);<br><br>async function verifyPaymentMethod() {<br>  try {<br>    const intent = await stripe.paymentIntents.create({<br>      amount: 100, // $1<br>      currency: &#39;usd&#39;,<br>      capture_method: &#39;manual&#39;,<br>      description: &#39;Verification of Payment Method&#39;,<br>      confirm: true,<br>      payment_method: &#39;pm_...&#39;,<br>      customer: &#39;cus_...&#39;,<br>    });<br><br>    if (intent.status === &#39;requires_capture&#39;) {<br>      // Payment method is verified<br>      return {<br>        intentId: intent.id,<br>        verified: true,<br>        requiresAsyncFlow: false,<br>      };<br>    }<br><br>    return {<br>      intentId: intent.id,<br>      verified: false,<br>      requiresAsyncFlow: true,<br>    };<br>  } catch (err) {<br>    return {<br>      intentId: null,<br>      verified: false,<br>      requiresAsyncFlow: false,<br>      errorMessage: err.message,<br>    };<br>  }<br>}</pre><p>To properly implement this code, it is necessary to replace the credentials with the appropriate values for the Stripe account. However, the overall structure and purpose of the code are clear and effective in explaining the concept.</p><p>The second part of the process involves setting up an HTTP listener that can receive the relevant events from Stripe’s webhook. We can handle this by writing a simple function that can parse and process the event data sent by Stripe. This function can then be invoked whenever Stripe sends a webhook event, allowing us to handle the event and take appropriate action as needed.</p><pre>const stripe = require(&#39;stripe&#39;)(&#39;sk_test_...&#39;);<br><br>async function handleEvent(event) {<br>  switch (event.data.object.status) {<br>    case &#39;payment_intent.amount_capturable_updated&#39;: {<br>      await stripe.paymentIntents.cancel(event.data.object.id);<br>      // notify the user about verification success<br>      break;<br>    }<br>    case &#39;payment_intent.payment_failed&#39;: {<br>      // notify the user about payment failure and possible issues with a credit card<br>      break;<br>    }<br>  }<br>}</pre><p>This implementation covers the main steps of the algorithm and should work for most use cases, but depending on the specifics of your project, you may need to add some additional logic or functionality.</p><p>It’s worth noting that Stripe provides an automatic cancellation feature for payment intents that aren’t canceled by our application due to unforeseen reasons such as downtime or mistake in the codebase. If such a situation occurs, the payment intent will be automatically canceled by Stripe after 7 days, which eliminates the possibility of blocking the user’s money without unlocking it back. This feature acts as an additional safeguard against accidental charge capture and provides reassurance to the users that their funds are safe in case of any unexpected events.</p><p>To <strong>summarize</strong>, the credit card validation process can be achieved by using the “Authorize and Capture” approach with Stripe. The algorithm consists of three steps: payment intent creation, client handling, and Stripe event handling via webhook. <strong><em>By following these steps, we can validate credit cards without incurring the high fees associated with test charges, ensuring a smooth payment process for our users</em></strong>.</p><p>Thank you for reading!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e0637a6732bf" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[First experience with AI assistant tool]]></title>
            <link>https://medium.com/@vviital/first-experience-with-ai-assistant-tool-f01330fc4fae?source=rss-151511378409------2</link>
            <guid isPermaLink="false">https://medium.com/p/f01330fc4fae</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[tabnine]]></category>
            <category><![CDATA[refactoring]]></category>
            <category><![CDATA[development]]></category>
            <category><![CDATA[nodejs]]></category>
            <dc:creator><![CDATA[Vitali Zaneuski]]></dc:creator>
            <pubDate>Mon, 20 Feb 2023 19:03:22 GMT</pubDate>
            <atom:updated>2023-02-20T19:03:22.665Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ND8txACuRNw20A2zMh2tag@2x.jpeg" /><figcaption>Photo by Possessed Photography on Unsplash</figcaption></figure><p>These days, it seems like everyone is talking about AI and its potential threats and benefits. I don’t want to join this “choir”, but I’m also looking for ways to improve my processes and make life easier. After attending a meetup about the future of development in early January, I decided to try using some AI tools at work. I did a quick half-hour of research, considering factors like privacy, maturity, and other options. In the end, I decided to give <a href="https://www.tabnine.com/">TabNine</a> a try.</p><p>Now, after using TabNine for a month, I’m ready to share my feedback and thoughts about it. To make things clearer, I’d like to split all of my code-related activities into four areas: <strong><em>business logic implementation</em></strong>, <strong><em>refactoring</em></strong>, <strong><em>testing</em></strong>, and <strong><em>writing documentation</em></strong>.</p><p>First, let’s talk about <strong><em>business logic implementation</em></strong>. At <a href="https://weareindy.com/">Indy</a>, we use both pure JavaScript and TypeScript for our services. In my experience, TabNine’s suggestions for TypeScript are better. In fact, I’ve learned some tricks related to the TS-type system that I had never seen before. While the business logic has good code completion, there is still room for improvement in some cases.</p><p>Let’s move on to <strong>refactoring</strong>. During the TabNine trial period, I had several tasks that involved refactoring or changing some parts of the system. One of the best things about the tool was how it understood which dependencies I wanted to import into the current file. For example, when I wrote const _ the tool automatically understood what I wanted to have const _ = require(&#39;lodash&#39;); in the end. This happened with almost every dependency that I had to work with. Another benefit of using TabNine is that it can help you reduce the work with boilerplate code. Instead of searching for commonly used code snippets in your project or typing them out manually, you can simply start typing the beginning of the code, and TabNine will suggest the rest. This feature can save you time and effort when working on a project, as it makes writing code faster and more efficient.</p><p>Let’s talk about <strong>tests</strong>. This is one of the best features of TabNine, especially when you need to implement several tests that are similar but with subtle differences, such as in some parameters. Tests are, by nature, simpler compared to business logic and often contain a lot of repeating code. I found that TabNine was able to generate some of the test files automatically by up to 50%. This saved me a lot of time and made the testing process much more efficient.</p><p>Last but not least, let’s talk about documentation writing. TabNine is incredibly helpful in this regard. It provides a similar experience to what Google has in their Gmail application when you’re composing an email. In many cases, it suggests exactly what I want to write, which has saved me a lot of time and made the process much smoother.</p><h4><strong>Results</strong></h4><p>The TabNine tool comes with a simple dashboard that displays statistics on its usage.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/810/1*i4c7B_gvF4EEQR2x6fhZ2g.png" /><figcaption>Stats from the TabNine admin panel</figcaption></figure><p>Currently, according to TabNine’s statistics, 37% of my code was automatically generated, and based on my experience, I believe this to be an accurate number. Since using TabNine, I’ve noticed a 10–15% improvement in my speed and efficiency, mostly due to a reduction in routine tasks. Overall, I’m quite pleased with the results and the performance boost that TabNine has provided.</p><p>If you have the option to use an AI tool to simplify your work on a project, I highly recommend taking advantage of it. These tools, such as TabNine, can help streamline your workflow and save you time and effort when coding.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f01330fc4fae" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to start using ECMAScript modules in a real-life Node.js project]]></title>
            <link>https://medium.com/@vviital/how-to-start-using-ecmascript-modules-in-a-real-life-node-js-project-c160d5a43da4?source=rss-151511378409------2</link>
            <guid isPermaLink="false">https://medium.com/p/c160d5a43da4</guid>
            <category><![CDATA[eslint]]></category>
            <category><![CDATA[modules]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[ecmascript]]></category>
            <dc:creator><![CDATA[Vitali Zaneuski]]></dc:creator>
            <pubDate>Sun, 15 Jan 2023 16:32:58 GMT</pubDate>
            <atom:updated>2023-01-15T16:32:58.463Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DHzfXRjYI-nXJtjV" /><figcaption>Photo by <a href="https://unsplash.com/@pinjasaur?utm_source=medium&amp;utm_medium=referral">Paul Esch-Laurent</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p><strong>NOTE</strong>: <strong><em>The simplified version of the project is available </em></strong><a href="https://github.com/vviital/esm-node-js-test"><strong><em>by the link</em></strong></a><strong><em>. All examples are taken from it.</em></strong></p><p>Periodically we <a href="https://weareindy.com/">at Indy</a> invest time in analyzing and updating dependencies to the latest versions as a part of the maintenance routine. So there is nothing special. One day during such an activity I’ve noticed that it was impossible to start the project after the simple upgrade.</p><p>The logs clearly spotted the culprit — the library <a href="https://www.npmjs.com/package/got"><strong>got</strong></a> changed its module system from the CommonJS to the ECMAScript modules.</p><pre>/Users/vitalizaneuski/.nvm/versions/node/v16.15.0/bin/node /Users/vitalizaneuski/projects/esm-test/broken-require.js<br>/Users/vitalizaneuski/projects/esm-test/broken-require.js:3<br>const got = require(&quot;got&quot;);<br>            ^<br><br>Error [ERR_REQUIRE_ESM]: require() of ES Module /Users/vitalizaneuski/projects/esm-test/node_modules/got/dist/source/index.js from /Users/vitalizaneuski/projects/esm-test/broken-require.js not supported.<br>Instead change the require of index.js in /Users/vitalizaneuski/projects/esm-test/broken-require.js to a dynamic import() which is available in all CommonJS modules.<br>    at Object.&lt;anonymous&gt; (/Users/vitalizaneuski/projects/esm-test/broken-require.js:3:13) {<br>  code: &#39;ERR_REQUIRE_ESM&#39;<br>}</pre><p>The author of the library did a good job and posted <a href="https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c">documentation</a> on how to start using the new version of the library, plus Node.js logs were also helpful. So it seems the solution was simple, just to replace the sync <strong>require</strong> by async dynamic <strong>import,</strong> and this situation should be sorted out. And it worked!</p><pre>/Users/vitalizaneuski/.nvm/versions/node/v16.15.0/bin/node /Users/vitalizaneuski/projects/esm-test/dynamic-import.js<br>{<br> &quot;login&quot;: &quot;mojombo&quot;,<br> &quot;id&quot;: 1,<br> &quot;node_id&quot;: &quot;MDQ6VXNlcjE=&quot;,<br> &quot;avatar_url&quot;: &quot;https://avatars.githubusercontent.com/u/1?v=4&quot;,<br> &quot;gravatar_id&quot;: &quot;&quot;,<br> &quot;url&quot;: &quot;https://api.github.com/users/mojombo&quot;,<br> &quot;html_url&quot;: &quot;https://github.com/mojombo&quot;,<br> &quot;followers_url&quot;: &quot;https://api.github.com/users/mojombo/followers&quot;,<br> &quot;following_url&quot;: &quot;https://api.github.com/users/mojombo/following{/other_user}&quot;,<br> &quot;gists_url&quot;: &quot;https://api.github.com/users/mojombo/gists{/gist_id}&quot;,<br> &quot;starred_url&quot;: &quot;https://api.github.com/users/mojombo/starred{/owner}{/repo}&quot;,<br> &quot;subscriptions_url&quot;: &quot;https://api.github.com/users/mojombo/subscriptions&quot;,<br> &quot;organizations_url&quot;: &quot;https://api.github.com/users/mojombo/orgs&quot;,<br> &quot;repos_url&quot;: &quot;https://api.github.com/users/mojombo/repos&quot;,<br> &quot;events_url&quot;: &quot;https://api.github.com/users/mojombo/events{/privacy}&quot;,<br> &quot;received_events_url&quot;: &quot;https://api.github.com/users/mojombo/received_events&quot;,<br> &quot;type&quot;: &quot;User&quot;,<br> &quot;site_admin&quot;: false<br>}<br><br>Process finished with exit code 0</pre><p>Yeah, the output above was the same as it was expected, but IDE was still complaining because of the ESLint.</p><pre>&gt; esm-node-js-test@1.0.0 lint<br>&gt; eslint .<br><br><br>/Users/vitalizaneuski/projects/esm-test/broken-require.js<br>  3:21  error  Unable to resolve path to module &#39;got&#39;  import/no-unresolved<br><br>/Users/vitalizaneuski/projects/esm-test/dynamic-import.js<br>  4:28  error  Unable to resolve path to module &#39;got&#39;  import/no-unresolved<br><br>✖ 2 problems (2 errors, 0 warnings)</pre><p>Many production-ready projects are using some kind of a linter and we <a href="https://weareindy.com/">at Indy</a> are not an exception to the rule. One of the plugins that we’re using is an <a href="https://www.npmjs.com/package/eslint-plugin-import">ESLint import plugin</a>. The error in the snippet was caused by the violation of the import/no-unresolved rule that checks whether the dependency is installed and can be resolved in production or not.</p><p>This error is a bit tricky to fix because we need to understand why it happens and how ESLint works and resolves packages. By default, eslint-import-plugin uses a <a href="https://www.npmjs.com/package/eslint-import-resolver-node">node resolver plugin</a> that is looking for the package.json in the project and trying to resolve the project entry point defined by the main property in the package.json . But the trick here is that new packages that are using ECMAScript modules are shipped with <a href="https://nodejs.org/dist/latest-v18.x/docs/api/packages.html#main-entry-point-export">exports</a> property instead of <a href="https://nodejs.org/dist/latest-v18.x/docs/api/packages.html#main">main</a>. And it makes ESLint check to fail.</p><p>Realizing the issue is easy to fix it — we just need to use another resolver plugin from the list of available ones. After a quick search in the npm, the following one was found — the <a href="https://www.npmjs.com/package/eslint-import-resolver-exports">exports resolver plugin</a> that adds missing functionality. The final ESLint configuration is below.</p><pre>&quot;use strict&quot;;<br><br>module.exports = {<br>  env: {<br>    node: true,<br>    es2020: true,<br>  },<br>  plugins: [&quot;import&quot;],<br>  settings: {<br>    &quot;import/resolver&quot;: {<br>      node: {<br>        extensions: [&quot;.js&quot;],<br>      },<br>      exports: {},<br>    },<br>  },<br>  rules: {<br>    &quot;import/no-unresolved&quot;: [&quot;error&quot;, { commonjs: true }],<br>  },<br>};</pre><p>That is it, the situation is resolved and the project now using packages baked by ECMAScript modules.</p><p><strong>NOTE:</strong> The refactoring can be intricate and I just described the case from my work. If you need to have some sync code that depends on the package, you need to rework the logic flow entirely, cause Node.js in CommonJS does not support root level await while import(&#39;package-name&#39;) only works asynchronously.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c160d5a43da4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Puppeteer vs PDFMake. How to generate PDFs using Node.js]]></title>
            <link>https://medium.com/@vviital/puppeteer-vs-pdfmake-how-to-generate-pdfs-using-node-js-794d1078b0b7?source=rss-151511378409------2</link>
            <guid isPermaLink="false">https://medium.com/p/794d1078b0b7</guid>
            <category><![CDATA[pdfmake]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[puppeteer]]></category>
            <category><![CDATA[backend]]></category>
            <category><![CDATA[performance]]></category>
            <dc:creator><![CDATA[Vitali Zaneuski]]></dc:creator>
            <pubDate>Fri, 16 Dec 2022 14:10:47 GMT</pubDate>
            <atom:updated>2022-12-16T14:10:47.549Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*BWP344JG0SxQL6f1" /><figcaption>Photo by <a href="https://unsplash.com/@wesleyphotography?utm_source=medium&amp;utm_medium=referral">Wesley Tingey</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3><strong>Intro</strong></h3><p>One of the common problems that I have been solving as a Software Engineer in several projects is how to generate PDFs and how to choose the proper approach. At <a href="https://weareindy.com/">Indy</a>, we have tried some approaches such as <a href="https://community.jaspersoft.com/">Jasper</a>, <a href="https://github.com/puppeteer/puppeteer">Puppeteer</a> &amp; <a href="http://pdfmake.org/#/">PDFMake</a>. In this article, I would like to compare two of them.</p><h3><strong>TLTR</strong></h3><p>Both <strong>puppeteer</strong> and <strong>PDFMake</strong> are good libraries to solve a PDF generation problem. PDFMake requires bigger involvement in maintenance due to the unique syntax, but at the same time shows greater performance in PDF generation (if we exclude context preparation: downloading of data from business services and image processing) for relatively small files. But PDFMake suffers when it comes to PDFs of big sizes (&gt; 5MB) with large numbers of images, while Chrome &amp; puppeteer pair process it way faster.</p><p>Puppeteer allows downloading PDFs from any page without knowing the details of page implementation, while for the PDFMake it’s required to know in advance what should be generated. Also, it’s up to the engineer to handle image download at the backend side, while Chrome does this routine for you.</p><p>PDFMake gives more granular control over the resulting document, such as header &amp; footer elements.</p><p>PDFMake supports stream API that theoretically allows handling bigger files compared to the Chrome engine, but at the same time for the PDFMake library you need to provide the whole document definition prior to the generation, so the benefits of streaming might be neglected.</p><p>It’s always up to the engineer to properly define the problem and choose the solution that works better in a particular case.</p><h3><strong>Puppeteer</strong></h3><blockquote>Puppeteer is a Node.js library which provides a high-level API to control Chrome/Chromium over the <a href="https://chromedevtools.github.io/devtools-protocol/">DevTools Protocol</a>. Puppeteer runs in <a href="https://developers.google.com/web/updates/2017/04/headless-chrome">headless</a> mode by default, but can be configured to run in full (non-headless) Chrome/Chromium.</blockquote><p>As mentioned in the official documentation Puppeteer is a library that allows us to communicate with a running Chrome/Chromium instance. This fact can be leveraged to generate PDFs using standard Chrome functionality (aka CTRL+P). All we need to do is to set up a connection to Chrome, open the page with content and generate PDF. The full code can be found by the <a href="https://github.com/vviital/pdf-generation-comparison/blob/main/src/chrome.ts">link</a>.</p><pre>const browser = await puppeteer.launch({headless: true});</pre><pre>const page = await browser.newPage();</pre><pre>// set up pdf content somehow</pre><pre>const dataBuffer = await page.pdf({format: &#39;a4&#39;}); // Buffer with generated PDF data</pre><pre>fs.writeFileSync(&#39;foo.pdf&#39;, recordSet);</pre><p>Four simple lines of code and you have your generated PDF file. The only missing piece here is how to set up the proper page or content for PDF. Puppeteer API has two useful methods for this perspective: page.goto with provided url and page.setContent with specified HTML content.</p><pre>const browser = await puppeteer.launch({headless: true});</pre><pre>const page = await browser.newPage();</pre><pre><strong>const html = `&lt;html&gt;&lt;body&gt;Hello world!&lt;/body&gt;&lt;/html&gt;`</strong></pre><pre><strong>await page.setContent(html, {waitUntil: [&#39;networkidle0&#39;]});</strong></pre><pre>const dataBuffer = await page.pdf({format: &#39;a4&#39;}); // Buffer with generated PDF data</pre><pre>fs.writeFileSync(&#39;foo.pdf&#39;, recordSet);</pre><p>That’s all, a simple PDF has been created.</p><h3><strong>PDFMake</strong></h3><blockquote><strong>pdfmake</strong></blockquote><blockquote>Client/server side PDF printing in pure JavaScript</blockquote><blockquote><strong>In-browser</strong></blockquote><blockquote>Print PDFs directly in the browser or delegate it to your NodeJS backend. Use the same document definition in both cases.</blockquote><blockquote><strong>Fully declarative</strong></blockquote><blockquote>Forget about manual x, y calculations. Declare document structure and let pdfmake do the rest.</blockquote><blockquote><strong>Powerful layout engine</strong></blockquote><blockquote>Use paragraphs, columns, lists, tables, canvas, etc… Declare your own styles, use custom fonts, build a DSL and extend the framework.</blockquote><p>The PDFMake library can be used both on the client &amp; server sides. The library has its own document syntax to create a PDF document definition that is a bit similar (but not equal) to the HTML. To convert HTML to its language we can use some libraries from the npm, e.g. <a href="https://www.npmjs.com/package/html-to-pdfmake">html-to-pdfmake</a>.</p><p>Here is a short example. The full code can be found by the <a href="https://github.com/vviital/pdf-generation-comparison/blob/main/src/pdfmake.ts">link</a>.</p><pre>const fonts = {<br>  Courier: {<br>    normal: &#39;Courier&#39;,<br>    bold: &#39;Courier-Bold&#39;,<br>    italics: &#39;Courier-Oblique&#39;,<br>    bolditalics: &#39;Courier-BoldOblique&#39;,<br>  },<br>  Helvetica: {<br>    normal: &#39;Helvetica&#39;,<br>    bold: &#39;Helvetica-Bold&#39;,<br>    italics: &#39;Helvetica-Oblique&#39;,<br>    bolditalics: &#39;Helvetica-BoldOblique&#39;,<br>  },<br>  Times: {<br>    normal: &#39;Times-Roman&#39;,<br>    bold: &#39;Times-Bold&#39;,<br>    italics: &#39;Times-Italic&#39;,<br>    bolditalics: &#39;Times-BoldItalic&#39;,<br>  },<br>  Symbol: {<br>    normal: &#39;Symbol&#39;,<br>  },<br>  ZapfDingbats: {<br>    normal: &#39;ZapfDingbats&#39;,<br>  },<br>};</pre><pre>const printer = new PdfPrinter(fonts);</pre><pre>const pdfStream = printer.createPdfKitDocument({<br>  defaultStyle: {<br>    font: &#39;Helvetica&#39;,<br>  },<br>  content: [{text: &#39;Hello world&#39;}],<br>});</pre><pre>pdfStream.end();<br><br>const chunks: Buffer[] = [];<br><br>for await (const chunk of pdfStream) {<br>  chunks.push(chunk as Buffer);<br>}</pre><pre>const dataBuffer = Buffer.concat(chunks);</pre><p>The PdfPrinter constructor accepts a list of supported fonts, while the printer instance can generate a stream of pdf chunks that should be collected to produce a final PDF buffer. This buffer can be used the same way as in the puppeteer’s example to record the file to the disk (for example).</p><h3><strong>Real-life usage</strong></h3><p>Everything is easy when you start reading examples, but how does it work in real life? As a Software Engineer, you should consider different aspects of the implementations. Let’s focus on some of them for now:</p><ul><li>Ability to fit business needs;</li><li>Maintenance;</li><li>Performance.</li></ul><h4>Ability to fit business needs</h4><p>Nobody needs a solution if it does nothing from the business perspective. Both approaches provide pretty good functionality to generate PDFs by provided templates and can work for major cases, in our case at <a href="https://weareindy.com/">Indy</a>, for example, we have found out that PDFmake provides a bit more extended possibilities to work with documents, e.g. more granular configurations for headers and footers. Also, it has plenty of interesting features such as<a href="https://pdfmake.github.io/docs/0.1/document-definition-object/security/"> PDF encryption</a> out of the box.</p><p>But anyway both of the solutions are mature and can be used in production based on use cases. If you need to create a PDF from the existing web page, puppeteer might be a better solution, especially when a web page has tons of async content or doesn’t belong to your organization directly.</p><h4>Maintenance</h4><p>When it comes to the maintenance of PDF generators it comes to the ways how you’re working with templates. The Puppeteer library supports two modes:</p><ul><li>Navigate to a page and when the page is loaded — create the PDF.</li><li>Propopulate the HTML with all needed content (e.g. images and business properties) and render a page by chrome/chromium using it. After that — create PDF out of it.</li></ul><p>The first approach can be solely handled on the client side by the Frontend or Full Stack Engineers using popular frameworks &amp; libraries, such as <a href="https://reactjs.org/">React</a>. All that backend needs to do — just open the proper link to make it work.</p><p>The second approach is similar to the first one but allows you to construct content solely on the server side, using any templating library or syntax that can be converted to HTML.</p><p>Both approaches are good since a team with any knowledge &amp; stack can find the best way for them.</p><p>PDFMake provides only one way to define the pdf document — generate proper object definition by the provided business entities. This approach is a bit similar to the 2nd for the puppeteer with subtle differences: e.g. to use images by the PDFMake on the server side you need to download it to the memory, while the puppeteer does this job by itself.</p><p>To use PDFMake properly the team needs to learn new syntax or use libraries that transform commonly known languages, such as <a href="https://www.npmjs.com/package/html-to-pdfmake">HTML to document definition</a>. At <a href="https://weareindy.com/">Indy</a>, we prefer using bare document definition to use the full power of the library.</p><h4>Performance</h4><p>The very last aspect that we focus on in the article — is the performance of the solution. Backend systems are tending to process requests from a large number of users and it’s essential to know what number of users the solution can sustain and what is expected amount of time to process a single request. Also, systems under load can experience different effects and show bottlenecks in the implementations.</p><p>To test the performance of puppeteer &amp; PDFMake libraries a simple <a href="https://github.com/vviital/pdf-generation-comparison">project has been developed</a>. Let’s define three types of loads:</p><ul><li>Small — a simple PDF for 1–2 pages with 1 image;</li><li>Medium — a medium-sized PDF for 10–15 pages with 10 images;</li><li>Large — a large-sized PDF for 60–70 pages with 50 images.</li></ul><p>To compare different implementations I’ve decided to run a script that will repeat the concurrent PDF generation for a given size and concurrency level. At the end of it, the average data can be collected. To have a similar environment &amp; resources the Docker container can be used.</p><p><strong>Preconditions</strong>:</p><ul><li>Tests are run under the Docker with 2 CPUs (2,4 GHz 8-Core Intel Core i9) &amp; 2 GB memory;</li><li>All images are served by the Nginx;</li><li>The cache for downloading images in Chrome is disabled to make the comparison a fair one;</li><li>Node.js process is allowed to make only 150 concurrent requests to the Nginx image service to not crush the application due to the high concurrency;</li><li>To gather average values for the final charts all loads were run 20 times.</li></ul><figure><img alt="Results for the “small” run" src="https://cdn-images-1.medium.com/max/1024/1*y_zCWiQ48wYNZ6-JMBCAww.png" /><figcaption>Results for the “small” run</figcaption></figure><figure><img alt="Results for the “medium” run" src="https://cdn-images-1.medium.com/max/1024/1*xFXQQ1vJnHXZAh5oz49Lug.png" /><figcaption>Results for the “medium” run</figcaption></figure><figure><img alt="Results for the “large” run" src="https://cdn-images-1.medium.com/max/1024/1*vocBn9apqmZVM3TpjBtEeQ.png" /><figcaption>Results for the “large” run</figcaption></figure><p>In the charts there are four lines:</p><ul><li>The “Chrome pdf generation” line represents the average time in milliseconds (Y axis) to generate concurrently X PDFs using the puppeteer library;</li><li>The “PDFMake pdf generation” line represents the average time in milliseconds (Y axis) to generate concurrently X PDFs using the PDFMake library;</li><li>The “PDFMake image download” line represents the average time in milliseconds (Y axis) to download images into the memory;</li><li>The “PDFMake pdf creation” line represents the average time in milliseconds (Y axis) to actually generate PDFs using PDFMake by provided data.</li></ul><p>After a couple of hours of waiting &amp; calculating results, I noticed some interesting facts.</p><p>The first fact is that PDFMake is extremely fast for small &amp; medium loads if we exclude image downloading time. The library can handle 50 concurrent requests under 300 ms or roughly 150 requests per second. But at the same time image preparation (download of 500 images) took almost 7 seconds for a medium load.</p><p>The second interesting fact is neither PDFMake nor puppeteer can deal with “large” loads efficiently. The chrome-based approach became unstable and start crashing or receiving timeouts for 11 concurrent requests. PDFMake was a bit better, but also crashed soon for 21 requests.</p><p>The third fact about PDFMake is that this library suffers when it comes to big PDFs. It took 5 seconds to generate a PDF of 8MB. Chrome rendered the same PDF 2.5 times faster.</p><h3><strong>Conclusions</strong></h3><p>Both <strong>puppeteer</strong> and <strong>PDFMake</strong> are good libraries to solve a PDF generation problem. PDFMake requires bigger involvement in maintenance due to the unique syntax, but at the same time shows greater performance in PDF generation (if we exclude context preparation: downloading of data from business services and image processing) for relatively small files. But PDFMake suffers when it comes to PDFs of big sizes (&gt; 5MB) with large numbers of images, while Chrome &amp; puppeteer pair process it way faster.</p><p>Puppeteer allows downloading PDFs from any page without knowing the details of page implementation, while for the PDFMake it’s required to know in advance what should be generated. Also, it’s up to the engineer to handle image download at the backend side, while Chrome does this routine for you.</p><p>PDFMake gives more granular control over the resulting document, such as header &amp; footer elements.</p><p>PDFMake supports stream API that theoretically allows handling bigger files compare to the Chrome engine, but at the same time for the PDFMake library you need to provide the whole document definition prior to the generation, so the benefits of streaming might be neglected.</p><p>It’s always up to the engineer to properly define the problem and choose the solution that works better in a particular case.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=794d1078b0b7" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>