<?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: Slava</title>
    <description>The latest articles on DEV Community by Slava (@vexell).</description>
    <link>https://dev.to/vexell</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%2F1219697%2Ff5464850-2b54-4327-ac22-97185fc1208e.jpg</url>
      <title>DEV Community: Slava</title>
      <link>https://dev.to/vexell</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vexell"/>
    <language>en</language>
    <item>
      <title>How to collect metrics from node.js applications in PM2 with exporting to Prometheus</title>
      <dc:creator>Slava</dc:creator>
      <pubDate>Mon, 12 Feb 2024 21:33:58 +0000</pubDate>
      <link>https://dev.to/vexell/how-to-collect-metrics-from-nodejs-applications-in-pm2-with-exporting-to-prometheus-3dbn</link>
      <guid>https://dev.to/vexell/how-to-collect-metrics-from-nodejs-applications-in-pm2-with-exporting-to-prometheus-3dbn</guid>
      <description>&lt;p&gt;It’s no secret that for stable and reliable working of node.js applications, it’s necessary to monitor their performance and get useful insights from their metrics. It means being able to collect information about app state before issues appear to preventing failures.&lt;/p&gt;

&lt;p&gt;In this article, I’d like to present a method for collecting metrics node.js applications from running in PM2 and exporting this data to Prometheus.&lt;/p&gt;

&lt;p&gt;When you simply start a node.js application using the &lt;code&gt;node&lt;/code&gt; command or through PM2 with the &lt;code&gt;pm2 start app.js&lt;/code&gt; command the new application is launched as a single instance. Collecting and exporting any metrics in this case would not be difficult. To do this, you can install the &lt;a href="https://www.npmjs.com/package/prom-client" rel="noopener noreferrer"&gt;prom-client&lt;/a&gt; package and add the metrics you need, for example, the number of requests to our application.&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;client&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prom-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;createServer&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="s1"&gt;http&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;registry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Registry&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;PREFIX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`nodejs_app_`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metricRequestsTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Counter&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PREFIX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;request_counter`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;help&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Show total request count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;registers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;registry&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 your application&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="nx"&gt;nodejsapp&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="s1"&gt;/*&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="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="nx"&gt;metricRequestsTotal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inc&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 Prom server for export metrics&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;promServer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createServer&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;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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentType&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;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;metrics&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="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;promServer&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="mi"&gt;9100&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="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="s1"&gt;Prom server started&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;In this example, when your application is launched, it also stars metric exporting on an additional port &lt;code&gt;9100&lt;/code&gt;. Of course you can use a separate URL with the main application, but it shouldn’t be public for users.&lt;/p&gt;

&lt;p&gt;This approach will work as long as you don’t need to run multiple instances of the application in cluster mode. For collecting metrics in cluster mode, prom-client provides &lt;a href="https://github.com/siimon/prom-client/blob/master/example/cluster.js" rel="noopener noreferrer"&gt;a good example&lt;/a&gt; with metric aggregation. But what if you’re running the application through the process manager PM2, where you don’t have access to the running cluster?&lt;/p&gt;

&lt;p&gt;In one of &lt;a href="https://vexell.medium.com/pm2-module-to-monitoring-node-js-application-with-export-to-prometheus-and-grafana-43d4b958c563" rel="noopener noreferrer"&gt;my previous articles&lt;/a&gt;, I talked about the &lt;a href="https://www.npmjs.com/package/pm2-prom-module" rel="noopener noreferrer"&gt;pm2-prom-module&lt;/a&gt;, which allows collecting overall statistics for PM2 applications but couldn’t collect internal statistics from each application. However, starting from version 2.0, such limitation is no longer present, and all statistics from PM2 and within the application are provided through a single module.&lt;/p&gt;

&lt;p&gt;o communicate between node.js application and &lt;a href="https://www.npmjs.com/package/pm2-prom-module" rel="noopener noreferrer"&gt;pm2-prom-module&lt;/a&gt;, you need to install the npm package &lt;a href="https://www.npmjs.com/package/pm2-prom-module-client" rel="noopener noreferrer"&gt;pm2-prom-module-client&lt;/a&gt; in your application. As a result, the example above will look like this:&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;client&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prom-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;initMetrics&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="s1"&gt;pm2-prom-module-client&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;registry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Registry&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;PREFIX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`nodejs_app_`&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;metricRequestCounter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Counter&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PREFIX&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;request_counter`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;help&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Show total request count&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;registers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Register your `registry` to send data to pm2-prom-module&lt;/span&gt;
&lt;span class="nf"&gt;initMetrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="nx"&gt;nodejsapp&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="s1"&gt;/*&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="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;// ...&lt;/span&gt;
    &lt;span class="nx"&gt;metricRequestCounter&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;inc&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now pm2-prom-module will provide PM2 statistics and also internal statistics from your applications.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

/// Common metrics from PM2

&lt;span class="c"&gt;# HELP pm2_free_memory Show available host free memory&lt;/span&gt;
&lt;span class="c"&gt;# TYPE pm2_free_memory gauge&lt;/span&gt;
pm2_free_memory&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-app"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 377147392

&lt;span class="c"&gt;# HELP pm2_cpu_count Show available CPUs count&lt;/span&gt;
&lt;span class="c"&gt;# TYPE pm2_cpu_count gauge&lt;/span&gt;
pm2_cpu_count&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-app"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 4

&lt;span class="c"&gt;# HELP pm2_available_apps Show available apps to monitor&lt;/span&gt;
&lt;span class="c"&gt;# TYPE pm2_available_apps gauge&lt;/span&gt;
pm2_available_apps&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-app"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 2

/// Apps metrics
&lt;span class="c"&gt;# HELP nodejs_app_request_counter Show total request count&lt;/span&gt;
&lt;span class="c"&gt;# TYPE nodejs_app_request_counter counter&lt;/span&gt;
nodejs_app_request_counter&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app1"&lt;/span&gt;,serviceName&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-app"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 10
nodejs_app_request_counter&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app2"&lt;/span&gt;,serviceName&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-app"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 17


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;By default, all statistics are aggregated across all running instances in every app, but if you want detailed statistics for each instance, you can enable this through the module’s configuration parameter:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

pm2 &lt;span class="nb"&gt;set &lt;/span&gt;pm2-prom-module:aggregate_app_metrics &lt;span class="nb"&gt;false&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This module, unlike some others, allows collecting metrics separately for each application, and they do not intersect.&lt;/p&gt;

&lt;p&gt;Overall, the statistics provided by PM2 are enough for basic application monitoring. However, in our case, for example, we wanted more detailed information about page rendering time or loading of certain data blocks. For such purposes, we used Histogram metric type.&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;// ...&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metricRequestTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Histogram&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="s1"&gt;nodejs_app_page_execute&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;help&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Time to processing request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;registers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;buckets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.7&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="na"&gt;labelNames&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="s1"&gt;code&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="s1"&gt;page&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="c1"&gt;// ...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endMetricRequestTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;metricRequestTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startTimer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="nf"&gt;endMetricRequestTime&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;code&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="na"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Homepage });


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In example above, we use 2 additional parameters: &lt;code&gt;code&lt;/code&gt; (HTTP response code) and &lt;code&gt;page&lt;/code&gt; (page identifier), which provide detailed statistics. For example, you can build such graphs in &lt;a href="https://grafana.com/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Most frequently pages &lt;code&gt;sum(rate(nodejs_app_page_execute_count{serviceName=”$serviceName”,code=”200"}[5m])) by (page)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The number of HTTP (response codes) success or errors. &lt;code&gt;sum(rate(nodejs_app_page_execute_count{serviceName=”$serviceName”}[5m])) by (code)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The page load time specifying, for example, the 99th percentile. &lt;code&gt;histogram_quantile(0.99, sum(rate(nodejs_app_page_execute_bucket{serviceName=”$serviceName”, code=”200"}[1m])) by (page, le))&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;And many other graphs for each individual page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the way, I don’t recommend using the URL of the page as the value for &lt;code&gt;page&lt;/code&gt; — it will significantly increase memory usage and the size of the response in Prometheus, and mess in the graphs. It’s better to use page identifier.&lt;/p&gt;

&lt;p&gt;So, to summarize, we have a single point for collecting and serving statistics for all applications running in PM2, and a monitoring system built on Prometheus with functional dashboards in Grafana.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fwraq0se9sl42qctbawxj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fwraq0se9sl42qctbawxj.png" alt="pm2-prom-module in Grafana"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you like &lt;strong&gt;pm2-prom-module&lt;/strong&gt;, I would appreciate if you could &lt;a href="https://github.com/VeXell/pm2-prom-module" rel="noopener noreferrer"&gt;star it on Github&lt;/a&gt;. Thanks for reading.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>node</category>
    </item>
    <item>
      <title>Auto scaling Node.js applications with PM2 and pm2-autoscale module</title>
      <dc:creator>Slava</dc:creator>
      <pubDate>Mon, 04 Dec 2023 20:51:03 +0000</pubDate>
      <link>https://dev.to/vexell/auto-scaling-nodejs-applications-with-pm2-and-pm2-autoscale-module-60h</link>
      <guid>https://dev.to/vexell/auto-scaling-nodejs-applications-with-pm2-and-pm2-autoscale-module-60h</guid>
      <description>&lt;p&gt;In this article, i would like to tell you how you can easily automatically scale your Node.js applications to handle increased traffic loads using PM2 and keep your server resources under control.&lt;/p&gt;

&lt;h2&gt;
  
  
  PM2 Cluster Mode
&lt;/h2&gt;

&lt;p&gt;PM2 is a process manager for Node.js applications that allows users to easily manage their Node.js applications and keep them running smoothly. With cluster mode PM2 allows networked Node.js applications to be scaled across all CPUs available, without any code modifications.&lt;/p&gt;

&lt;p&gt;For example, if you have server with 8 CPUs you can run 8 instances of your application and increase the performance and reliability of your application.&lt;/p&gt;

&lt;p&gt;To run application in cluster mode you can easily create &lt;a href="https://pm2.keymetrics.io/docs/usage/application-declaration/"&gt;ecosystem configuration file&lt;/a&gt; and start you application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;module.exports &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    apps: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
            name: &lt;span class="s1"&gt;'app'&lt;/span&gt;,
            script: &lt;span class="s1"&gt;'build/app.js'&lt;/span&gt;,
            instances: &lt;span class="s1"&gt;'8'&lt;/span&gt;,
            autorestart: &lt;span class="nb"&gt;true&lt;/span&gt;,
            watch: &lt;span class="nb"&gt;false&lt;/span&gt;,
            max_memory_restart: &lt;span class="s1"&gt;'512M'&lt;/span&gt;,
            vizion: &lt;span class="nb"&gt;false&lt;/span&gt;,
            exec_mode: &lt;span class="s1"&gt;'cluster'&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="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration allows you run 8 workers on your server. And for most cases it would be enough (especially if you have only one application on your server).&lt;/p&gt;

&lt;p&gt;But imagine you want to add one more application to your server. Of course you want split resources between your applications. Now you change configuration file for every app and set 4 workers for every application. It will work until any of your applications can handle increased traffic load. If one of the application uses 100% CPU of every worker you should add more workers. In that case you should connect to your server and use &lt;code&gt;pm2 scale&lt;/code&gt; command to scale more workers for loaded app and decrease workers for another app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 scale app +3 &lt;span class="c"&gt;# Add 3 new additional instances for your app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When application load has been decreased, you also should connect to the server and revert your changes back. Not comfortable and easy solution. Plus you have to have solution to monitor you applications to detect when you need scale one of your apps.&lt;/p&gt;

&lt;p&gt;Another case, for example, you have server with 48 CPUs and you want to run multiple apps on it. If you run every application with instances=max PM2 will run 48 instances and imagine that every instance uses approximately 100Mb of RAM (~5GB for all instances). So if you have 10 application it means you will use about 50GB of the server memory without any server load. You are using your server resources ineffectively.&lt;/p&gt;

&lt;p&gt;Unfortunately PM2 does not have any good and simple solutions how to dynamically increase workers and monitor your applications. And with free version you can see CPU utilization and Memory usage &lt;a href="https://pm2.keymetrics.io/docs/usage/monitoring/"&gt;only in terminal&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  PM2 Autoscale Module
&lt;/h2&gt;

&lt;p&gt;To solve this issue i wrote plugin &lt;a href="https://www.npmjs.com/package/pm2-autoscale"&gt;pm2-autoscale&lt;/a&gt; that helps to optimize application’s performance by automatically adjusting the number of instances running based on the CPU utilization of every application.&lt;/p&gt;

&lt;p&gt;To use &lt;a href="https://www.npmjs.com/package/pm2-autoscale"&gt;pm2-autoscale&lt;/a&gt; module your should install it with command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Module supports few configuration options&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;scale_cpu_threshold&lt;/code&gt; Maximum value of CPU utilization one of application instances when the module will try to increase application instances. (default to 30)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;release_cpu_threshold&lt;/code&gt; Average value of all CPUs utilization of the application when the module will decrease application instances (default to 5)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;debug&lt;/code&gt; Enable debug mode to show logs from the module (default to false)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To modify the module config values you can use the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pm2 &lt;span class="nb"&gt;set &lt;/span&gt;pm2-autoscale:debug &lt;span class="nb"&gt;true
&lt;/span&gt;pm2 &lt;span class="nb"&gt;set &lt;/span&gt;pm2-autoscale:scale_cpu_threshold 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can modify your ecosystem configuration to run any application with minimum required instances. For example, if you have 8 CPUs and 2 applications, you can set &lt;code&gt;instances=2&lt;/code&gt; for every application and keep available resources of your server. When module detects that CPU utilisation is higher then &lt;code&gt;scale_cpu_threshold&lt;/code&gt; it will start increasing instances to max CPUs-1 only if server has available free memory. When module detects CPU utilization is decreasing it will stop useless instances.&lt;/p&gt;

&lt;p&gt;See example how it works. I tested it with Apache Benchmark.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0|pm2-autoscale | App "app" has 1 worker(s). CPU: 0.
0|pm2-autoscale | App "app" has 1 worker(s). CPU: 17.
0|pm2-autoscale | App "app" has 1 worker(s). CPU: 29.
0|pm2-autoscale | INFO: Increase workers
0|pm2-autoscale | App "app" scaled with +1 worker
0|pm2-autoscale | App "app" has 2 worker(s). CPU: 26,34.
0|pm2-autoscale | App "app" has 2 worker(s). CPU: 29,43.
0|pm2-autoscale | App "app" has 2 worker(s). CPU: 28,38.
0|pm2-autoscale | INFO: Increase workers
0|pm2-autoscale | INFO: App "app" is busy
0|pm2-autoscale | App "app" scaled with +1 worker
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 27,35,33.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 25,31,22.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 24,29,23.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 23,25,20.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 15,18,17.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 11,13,13.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 8,9,10.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 6,7,7.
0|pm2-autoscale | App "app" has 3 worker(s). CPU: 5,5,5.
0|pm2-autoscale | INFO: Decrease workers
0|pm2-autoscale | INFO: App "app" is busy
0|pm2-autoscale | App "app" decresed one worker
0|pm2-autoscale | App "app" has 2 worker(s). CPU: 4,4.
0|pm2-autoscale | App "app" has 2 worker(s). CPU: 3,3.
0|pm2-autoscale | App "app" has 2 worker(s). CPU: 2,2.
0|pm2-autoscale | INFO: Decrease workers
0|pm2-autoscale | INFO: App "app" is busy
0|pm2-autoscale | App "app" decresed one worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thus, if the CPU usage of the application is high, the module can increase the number of instances running to handle the load. Similarly, if the CPU usage is low, the module can reduce the number of instances running to save resources.&lt;/p&gt;

&lt;p&gt;Source code of the module is available on GitHub &lt;a href="https://github.com/VeXell/pm2-autoscale"&gt;https://github.com/VeXell/pm2-autoscale&lt;/a&gt; and if you want to add/change something — just create a pull request.&lt;/p&gt;

&lt;p&gt;Thank you for reading this article. Hope you found this useful.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>devops</category>
      <category>node</category>
      <category>frontend</category>
    </item>
    <item>
      <title>PM2 module to monitoring node.js application with export to Prometheus and Grafana</title>
      <dc:creator>Slava</dc:creator>
      <pubDate>Wed, 29 Nov 2023 12:18:46 +0000</pubDate>
      <link>https://dev.to/vexell/pm2-module-to-monitoring-nodejs-application-with-export-to-prometheus-and-grafana-5c9k</link>
      <guid>https://dev.to/vexell/pm2-module-to-monitoring-nodejs-application-with-export-to-prometheus-and-grafana-5c9k</guid>
      <description>&lt;p&gt;Monitoring a node.js application is one of the crucial stages in analyzing the quality and proper functioning of the application. It allows you to identify ongoing issues and quickly make decisions to fix them.&lt;/p&gt;

&lt;p&gt;PM2 provides internal tools for monitoring all your applications, but in the free version, they are only accessible through the console command &lt;code&gt;pm2 monit&lt;/code&gt;. Alternatively, PM2 offers a subscription service called PM2 Plus with additional functionality.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fe8h7civib6wpa143e4zt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fe8h7civib6wpa143e4zt.png" alt="PM2 Monit command"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, neither of the mentioned methods are suitable for monitoring because you need to see not only current performance metrics but also changes in values over time (for example, the growth of memory usage within the application or the number of its reloads). Also payment subscription does not integrated into your overall monitoring system and located on separated service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F2wq22equjxazi4tcpfwm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F2wq22equjxazi4tcpfwm.png" alt="PM2 Monitoring"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In most cases, applications use the combination of &lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt; + &lt;a href="https://grafana.com/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt;, which allows collect data and display it in the form of graphs and also to set up alerts for changes in any metrics.&lt;/p&gt;

&lt;p&gt;Unfortunately, PM2 does not provide a convenient and simple way to export data to Prometheus, so it was a motivation to implement my own module that would allow doing this. In addition, this module should be able to export additional metrics which my another module &lt;a href="https://www.npmjs.com/package/pm2-autoscale" rel="noopener noreferrer"&gt;pm2-autoscale&lt;/a&gt;, relies.&lt;/p&gt;

&lt;p&gt;Finally &lt;a href="https://www.npmjs.com/package/pm2-prom-module" rel="noopener noreferrer"&gt;pm2-prom-module&lt;/a&gt; has been released and available for everyone. You can install it just with command &lt;code&gt;pm2 install&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

pm2 &lt;span class="nb"&gt;install &lt;/span&gt;pm2-prom-module


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After installation monitoring service will be available on port &lt;code&gt;9988&lt;/code&gt; and you can reach it by url &lt;code&gt;http://localhost:9988/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Of course you can configure module and change port and service name, for example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

pm2 &lt;span class="nb"&gt;set &lt;/span&gt;pm2-prom-module:port 10801
pm2 &lt;span class="nb"&gt;set &lt;/span&gt;pm2-prom-module:service_name MyApp


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Specifying the &lt;code&gt;service_name&lt;/code&gt; is very convenient when installing the module across different projects, while using a single dashboard in Grafana. This allows you to create a dropdown list with all the services and quick switch between them.&lt;/p&gt;

&lt;p&gt;This module collect all available metrics provided by PM2 and a few additional ones. The complete list looks something like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Available free memory&lt;/li&gt;
&lt;li&gt;Number of CPUs&lt;/li&gt;
&lt;li&gt;Number of running applications&lt;/li&gt;
&lt;li&gt;Number of instances for each application&lt;/li&gt;
&lt;li&gt;Average memory usage per application&lt;/li&gt;
&lt;li&gt;Total memory used for each application&lt;/li&gt;
&lt;li&gt;Average CPU load for each application&lt;/li&gt;
&lt;li&gt;Current CPU load for each application&lt;/li&gt;
&lt;li&gt;Number of restarts for each application&lt;/li&gt;
&lt;li&gt;Uptime for each application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As well as all the statistics within PM2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Used Heap Size&lt;/li&gt;
&lt;li&gt;Heap Usage&lt;/li&gt;
&lt;li&gt;Heap Size&lt;/li&gt;
&lt;li&gt;Event Loop Latency p95&lt;/li&gt;
&lt;li&gt;Event Loop Latency&lt;/li&gt;
&lt;li&gt;Active handles&lt;/li&gt;
&lt;li&gt;Active requests&lt;/li&gt;
&lt;li&gt;HTTP req/min&lt;/li&gt;
&lt;li&gt;HTTP P95 Latency&lt;/li&gt;
&lt;li&gt;HTTP Mean Latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes, PM2 returns very peculiar data that requires double-checking, such as Active requests or HTTP req/min (in reality, it seems more like req/sec).&lt;/p&gt;

&lt;p&gt;Here is example of module output:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="c"&gt;# HELP pm2_free_memory Show available host free memory&lt;/span&gt;
&lt;span class="c"&gt;# TYPE pm2_free_memory gauge&lt;/span&gt;
pm2_free_memory&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-app"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 377147392

&lt;span class="c"&gt;# HELP pm2_cpu_count Show available CPUs count&lt;/span&gt;
&lt;span class="c"&gt;# TYPE pm2_cpu_count gauge&lt;/span&gt;
pm2_cpu_count&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-app"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 4

&lt;span class="c"&gt;# HELP pm2_available_apps Show available apps to monitor&lt;/span&gt;
&lt;span class="c"&gt;# TYPE pm2_available_apps gauge&lt;/span&gt;
pm2_available_apps&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-app"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 1

&lt;span class="c"&gt;# HELP pm2_app_instances Show app instances count&lt;/span&gt;
&lt;span class="c"&gt;# TYPE pm2_app_instances gauge&lt;/span&gt;
pm2_app_instances&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;,serviceName&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-app"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 2

&lt;span class="c"&gt;# HELP pm2_app_average_memory Show average using memory of an app&lt;/span&gt;
&lt;span class="c"&gt;# TYPE pm2_app_average_memory gauge&lt;/span&gt;
pm2_app_average_memory&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;,serviceName&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-app"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 60813927

&lt;span class="c"&gt;# HELP pm2_app_total_memory Show total using memory of an app&lt;/span&gt;
&lt;span class="c"&gt;# TYPE pm2_app_total_memory gauge&lt;/span&gt;
pm2_app_total_memory&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;,serviceName&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-app"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 121626624

&lt;span class="c"&gt;# HELP pm2_event_loop_latency_p95 Event Loop Latency p95. Unit "ms"&lt;/span&gt;
&lt;span class="c"&gt;# TYPE pm2_event_loop_latency_p95 gauge&lt;/span&gt;
pm2_event_loop_latency_p95&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;,instance&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;,serviceName&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-app"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 2.55
pm2_event_loop_latency_p95&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"app"&lt;/span&gt;,instance&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;,serviceName&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my-app"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 2.48


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally you can have Grafana dashboard with detailed statistic of your application. If you like it you can &lt;a href="https://vexell.ru/content/files/2023/09/grafana-model.json" rel="noopener noreferrer"&gt;download this dashboard from this link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fchw4wnjsvqc3cve58zvi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fchw4wnjsvqc3cve58zvi.png" alt="Grafana with pm2-prom-module"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our case, each node.js project is packaged into a Docker container, and inside, we install PM2 and additional modules. All parameters, such as the service name or port, are passed through arguments. As a result, the Docker container looks something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

FROM node:18-bullseye-slim 

&lt;span class="c"&gt;#### Install PM2&lt;/span&gt;
RUN npm i &lt;span class="nt"&gt;-g&lt;/span&gt; pm2@5.2.2

&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;span class="c"&gt;# Build project&lt;/span&gt;
&lt;span class="c"&gt;#...&lt;/span&gt;

ARG PROJECT_NAME
ARG ENV_METRICS_PORT

&lt;span class="c"&gt;#### Install modules for PM2&lt;/span&gt;
RUN pm2 &lt;span class="nb"&gt;install &lt;/span&gt;pm2-autoscale &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pm2 &lt;span class="nb"&gt;install &lt;/span&gt;pm2-prom-module &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pm2 &lt;span class="nb"&gt;set &lt;/span&gt;pm2-prom-module:port &lt;span class="nv"&gt;$ENV_METRICS_PORT&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pm2 &lt;span class="nb"&gt;set &lt;/span&gt;pm2-prom-module:service_name &lt;span class="nv"&gt;$PROJECT_NAME&lt;/span&gt;

CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"pm2-runtime"&lt;/span&gt;, &lt;span class="s2"&gt;"--json"&lt;/span&gt;, &lt;span class="s2"&gt;".ecosystem.config.js"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>devops</category>
      <category>node</category>
    </item>
    <item>
      <title>How to quickly generate dynamic images with Node.js and Puppeteer.</title>
      <dc:creator>Slava</dc:creator>
      <pubDate>Mon, 27 Nov 2023 15:28:17 +0000</pubDate>
      <link>https://dev.to/vexell/how-to-quickly-generate-dynamic-images-with-nodejs-and-puppeteer-4o4n</link>
      <guid>https://dev.to/vexell/how-to-quickly-generate-dynamic-images-with-nodejs-and-puppeteer-4o4n</guid>
      <description>&lt;p&gt;In our time, numerous websites create pages that users share in different social networks or messengers. Thanks to Open Graph tags, links can have a preview image that attracts even more attention, for example with &lt;code&gt;og:image&lt;/code&gt; tag. But usually, many websites don’t put much effort into preview images and simply add one image to the majority of pages. If there is no image, parsers try to automatically find the first available suitable image and use it.&lt;/p&gt;

&lt;p&gt;But imagine how great it would be to have a personalized image on the page, for example, when you have profiles of your users or events in which they participate.&lt;/p&gt;

&lt;p&gt;For example, take a look at the image below that we generate for sharing on social networks. It has a custom font, gradient, and even localization of text on the image into different languages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fl2jymyviw3kcyjoyjuuv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fl2jymyviw3kcyjoyjuuv.png" alt="Open Graph image for user profile"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our project, the initial versions of images were generated using PHP, and for a while, this was sufficient because the images were quite simple and only contained the user’s picture. However, as soon as we added the user’s name, problems with text positioning immediately arose. For instance, here’s a small example of creating a simple image in PHP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="nv"&gt;$width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;imagecreatetruecolor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;imagecolorallocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;imagefill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image&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;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$backgroundColor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$textColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;imagecolorallocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image&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;0&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;// Load a custom TrueType font&lt;/span&gt;
&lt;span class="nv"&gt;$fontFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'path/to/your/font.ttf'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Set the text to be displayed (considering localization)&lt;/span&gt;
&lt;span class="nv"&gt;$language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'lang'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'lang'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getLocalizedText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$language&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$fontSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Set the position for the text to be displayed&lt;/span&gt;
&lt;span class="nv"&gt;$textbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;imagettfbbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fontSize&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="nv"&gt;$fontFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$textX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$textbox&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="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$textbox&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="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="nv"&gt;$textY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$textbox&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="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$textbox&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="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="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$textbox&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="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$textbox&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="nb"&gt;imagettftext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$fontSize&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="nv"&gt;$textX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$textY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$textColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$fontFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Content-Type: image/png"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;imagepng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;imagedestroy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getLocalizedText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'en'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'Hello, PHP!'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'fr'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'Bonjour, PHP!'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'Hello, PHP!'&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 seen from the example, if the text is too long or if you would like change its size, it may extend beyond the boundaries of the image. And if you add gradients, transparency, different fonts, emojis, the code will become complex and challenging to maintain.&lt;/p&gt;

&lt;p&gt;The solution I would like to present allows you to reduce development time and simplifies the maintenance of such images. Moreover, it is flexible and easily scalable for other purposes.&lt;/p&gt;

&lt;p&gt;So every web developers knows that HTML is the simplest and most convenient markup language. Along with CSS styles, you can create an interface that is flexible and takes into account the positioning of any elements on the page — images, text, tables, lists and etc.&lt;/p&gt;

&lt;p&gt;Let’s have a look and on the example below. This is simple HTML page with many css styles like gradient in text, shadows, limiting text rows for long text and other.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0093E9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;160deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#0093E9&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#80D0C7&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Helvetica&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nt"&gt;header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nt"&gt;header&lt;/span&gt; &lt;span class="nc"&gt;.emoji&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;-10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3rem&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="p"&gt;{&lt;/span&gt;
                &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nl"&gt;text-transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;uppercase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;-webkit-linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;45deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#85FFBD&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#FFFB7D&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nl"&gt;-webkit-background-clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;-webkit-text-fill-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nc"&gt;.wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;padding-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nc"&gt;.avatar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nc"&gt;.avatar&lt;/span&gt; &lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;140px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;140px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nl"&gt;object-fit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cover&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nc"&gt;.content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="nc"&gt;.content&lt;/span&gt; &lt;span class="nc"&gt;.text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;-webkit-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;-webkit-line-clamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* number of lines to show */&lt;/span&gt;
                        &lt;span class="py"&gt;line-clamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
                &lt;span class="nl"&gt;-webkit-box-orient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello, Javascript&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"emoji"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;🤖&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"avatar"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://sun9-57.userapi.com/impg/O3egMIWPZjhcKSThZ2hn7ByaQmET8ySOq5e4ww/O_ngP3qqEd8.jpg?size=1178x1789&amp;amp;quality=95&amp;amp;sign=71fcbf49ffff80fad9f0ef39f598cf69&amp;amp;type=album"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Lorem Ipsum&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;  is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we need it is run a web server which can serve this HTML code and run node.js application with Puppeteer library, which launches a headless version of Google Chrome, an then you can take a screenshot of the page and get the desired image.&lt;/p&gt;

&lt;p&gt;Below is an example code that allows you to capture the content of a page with specified width, height and scale. It is crucial to handle exceptions and close the browser pages if something goes wrong; otherwise, it may lead to a memory leak.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;headless&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;ignoreHTTPSErrors&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;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;browserPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;args&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="s1"&gt;--no-sandbox&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="s1"&gt;--disable-setuid-sandbox&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;dumpio&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setJavaScriptEnabled&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;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setViewport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;deviceScaleFactor&lt;/span&gt;&lt;span class="p"&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&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="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load&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;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;status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
       &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PageNotFoundError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Incorrect status page &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;status&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;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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PageFetchError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;imageType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;imageQuality&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;binary&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally as result you can get image like that which you can upload to you S3 storage and serve it to users. This is really simple to support and manage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fqrelv2lfz47ldzeabjal.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fqrelv2lfz47ldzeabjal.png" alt="Example of image generation with Puppeteer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our case we create a small node.js application with Puppeteer that has been packaged into a Docker container and run HTTP server inside to manage external requests. This application allows us to generate images with various format (like png, jpgor webp )for any page of the website.&lt;/p&gt;

&lt;p&gt;So, the full logic of service for OG images generation might be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When your React app is rendering a page your generate special link in html meta tags &lt;code&gt;og:image&lt;/code&gt; (with user id, required image size and extension) to nginx proxy.&lt;/li&gt;
&lt;li&gt;The proxy server checks if the image is already in the S3 storage or not.&lt;/li&gt;
&lt;li&gt;If the image exists, it is served; if not, a request is made to the node.js service, which generates the image, serves it, and asynchronously uploads it to S3.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;During the development we faced few minor problems such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chromium issues on a Ubuntu Docker container were resolved by directly downloading Google Chrome.&lt;/li&gt;
&lt;li&gt;Font compatibility: one of our fonts broke Apple emoji font. We decided to replace it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Despite minor technical hurdles, the new node.js service significantly reduced image generation and support time compared to the previous PHP code. The solution allows us quickly update images, just change HTML code, simple to test it in the browser and use all power of CSS.&lt;/p&gt;

&lt;p&gt;It’s a great way to achieve flexibility and scalability in generating images dynamically for any of your purposes (not only to generate Open Graph images).&lt;/p&gt;

&lt;p&gt;If you have any more questions or if there’s anything else I can help you with, feel free to ask!&lt;/p&gt;

&lt;p&gt;Here is example of docker container which we are using with all required libraries to work with images.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;FROM ubuntu:20.04

RUN &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /usr/share/zoneinfo/Etc/UTC /etc/localtime &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt &lt;span class="nt"&gt;-y&lt;/span&gt; update &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  git &lt;span class="se"&gt;\&lt;/span&gt;
        openssh-server &lt;span class="se"&gt;\&lt;/span&gt;
  gconf-service &lt;span class="se"&gt;\&lt;/span&gt;
  libasound2 &lt;span class="se"&gt;\&lt;/span&gt;
  libatk1.0-0 &lt;span class="se"&gt;\&lt;/span&gt;
  libc6 &lt;span class="se"&gt;\&lt;/span&gt;
  libcairo2 &lt;span class="se"&gt;\&lt;/span&gt;
  libcups2 &lt;span class="se"&gt;\&lt;/span&gt;
  libdbus-1-3 &lt;span class="se"&gt;\&lt;/span&gt;
  libexpat1 &lt;span class="se"&gt;\&lt;/span&gt;
  libfontconfig1 &lt;span class="se"&gt;\&lt;/span&gt;
  libgcc1 &lt;span class="se"&gt;\&lt;/span&gt;
  libgconf-2-4 &lt;span class="se"&gt;\&lt;/span&gt;
  libgdk-pixbuf2.0-0 &lt;span class="se"&gt;\&lt;/span&gt;
  libglib2.0-0 &lt;span class="se"&gt;\&lt;/span&gt;
  libgtk-3-0 &lt;span class="se"&gt;\&lt;/span&gt;
  libnspr4 &lt;span class="se"&gt;\&lt;/span&gt;
  libpango-1.0-0 &lt;span class="se"&gt;\&lt;/span&gt;
  libpangocairo-1.0-0 &lt;span class="se"&gt;\&lt;/span&gt;
  libstdc++6 &lt;span class="se"&gt;\&lt;/span&gt;
  libx11-6 &lt;span class="se"&gt;\&lt;/span&gt;
  libx11-xcb1 &lt;span class="se"&gt;\&lt;/span&gt;
  libxcb1 &lt;span class="se"&gt;\&lt;/span&gt;
  libxcomposite1 &lt;span class="se"&gt;\&lt;/span&gt;
  libxcursor1 &lt;span class="se"&gt;\&lt;/span&gt;
  libxdamage1 &lt;span class="se"&gt;\&lt;/span&gt;
  libxext6 &lt;span class="se"&gt;\&lt;/span&gt;
  libxfixes3 &lt;span class="se"&gt;\&lt;/span&gt;
  libxi6 &lt;span class="se"&gt;\&lt;/span&gt;
  libxrandr2 &lt;span class="se"&gt;\&lt;/span&gt;
  libxrender1 &lt;span class="se"&gt;\&lt;/span&gt;
  libxss1 &lt;span class="se"&gt;\&lt;/span&gt;
  libxtst6 &lt;span class="se"&gt;\&lt;/span&gt;
  ca-certificates &lt;span class="se"&gt;\&lt;/span&gt;
  fonts-liberation &lt;span class="se"&gt;\&lt;/span&gt;
  libappindicator1 &lt;span class="se"&gt;\&lt;/span&gt;
  libnss3 &lt;span class="se"&gt;\&lt;/span&gt;
  lsb-release &lt;span class="se"&gt;\&lt;/span&gt;
  xdg-utils &lt;span class="se"&gt;\&lt;/span&gt;
  wget &lt;span class="se"&gt;\&lt;/span&gt;
  curl &lt;span class="se"&gt;\&lt;/span&gt;
  libnss3-dev &lt;span class="se"&gt;\&lt;/span&gt;
  libgbm-dev &lt;span class="se"&gt;\&lt;/span&gt;
  libu2f-udev &lt;span class="se"&gt;\&lt;/span&gt;
  udev &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://deb.nodesource.com/setup_14.x | bash -&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nodejs &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt &lt;span class="nb"&gt;install&lt;/span&gt; ./google-chrome-stable_current_amd64.deb &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; ./google-chrome-stable_current_amd64.deb &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get clean &lt;span class="se"&gt;\&lt;/span&gt;
 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/cache/apt/lists

&lt;span class="c"&gt;# Add new fonts&lt;/span&gt;
COPY ./fonts /root/.fonts
RUN fc-cache &lt;span class="nt"&gt;-fv&lt;/span&gt;

&lt;span class="c"&gt;# Build and run your node.js app&lt;/span&gt;
...

&lt;span class="c"&gt;# Run node&lt;/span&gt;
CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"node"&lt;/span&gt;, &lt;span class="s2"&gt;"app.js"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>tutorial</category>
      <category>node</category>
      <category>frontend</category>
    </item>
  </channel>
</rss>
