<?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: Sorin Curescu</title>
    <description>The latest articles on DEV Community by Sorin Curescu (@en3sis).</description>
    <link>https://dev.to/en3sis</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%2F188120%2Ff87ff5a3-352f-4994-a71a-f4bfbfabdaf6.png</url>
      <title>DEV Community: Sorin Curescu</title>
      <link>https://dev.to/en3sis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/en3sis"/>
    <language>en</language>
    <item>
      <title>Advanced Discord.js: Cache APIs Requests with Redis</title>
      <dc:creator>Sorin Curescu</dc:creator>
      <pubDate>Fri, 23 Apr 2021 09:00:00 +0000</pubDate>
      <link>https://dev.to/en3sis/advanced-discord-js-cache-apis-requests-with-redis-4d8a</link>
      <guid>https://dev.to/en3sis/advanced-discord-js-cache-apis-requests-with-redis-4d8a</guid>
      <description>&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Getting started

&lt;ul&gt;
&lt;li&gt;Installation&lt;/li&gt;
&lt;li&gt;Usage&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Cache data from APIs

&lt;ul&gt;
&lt;li&gt;Preparation&lt;/li&gt;
&lt;li&gt;
Solving the problem

&lt;ul&gt;
&lt;li&gt;Implementation&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

Wrapping up

&lt;ul&gt;
&lt;li&gt;Real-world usecases&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;This article is part of a series that can be found &lt;a href="https://dev.to/en3sis/series/10809"&gt;here&lt;/a&gt;. We're reusing some code from the &lt;a href="https://dev.to/en3sis/advanced-discord-js-custom-embeds-using-attachments-2bpn"&gt;previous article&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Looking for a Discord.JS bot? &lt;a href="https://github.com/en3sis/hans" rel="noopener noreferrer"&gt;Check out Hans&lt;/a&gt;! Now it's open-sourced 🥳!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/en3sis/hans" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fvac%2Fimage%2Fupload%2Fv1683578541%2FHans%2Fhans-banner-pattern.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Nowadays we depend on many APIs that we interact with (weather, games stats, etc...)&lt;/p&gt;

&lt;p&gt;Many times we don't have to worry about rate limits but in some cases, we do.&lt;br&gt;
If the API has a low rate limit (e.g. &lt;code&gt;x&lt;/code&gt; amount of requests per minute) and if we want to deliver the maximum amount of data to our users at some point caching could be the best way to do it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: The other option would be to store it in a persistent database such as MongoDB or SQLite, but this would be slower and we add extra network requests if we use an external service&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Getting started &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;First of all, what is Redis?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Redis is an in-memory data structure store, used as a distributed, in-memory key–value database, cache and message broker, with optional durability. - &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;redis.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This looks promising!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's storing data in memory so it will be amazingly fast to read/write.&lt;/li&gt;
&lt;li&gt;We can temporally store data (it can also be persistent).
For us we're interested in temporary caching, we don't want to show outdated data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: You can check out the guide for &lt;a href="https://medium.com/@petehouston/install-and-config-redis-on-mac-os-x-via-homebrew-eb8df9a4f298" rel="noopener noreferrer"&gt;MacOS&lt;/a&gt; or &lt;a href="https://redislabs.com/blog/redis-on-windows-10/" rel="noopener noreferrer"&gt;Windows&lt;/a&gt;. More information is available at the &lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;official website&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Installation &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Now that we have Redis running in our system, we can now grab the node package:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm i redis&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It's time to test it out!&lt;br&gt;
We can write a new command that will set a temporary key with the data for us.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: Redis values can store strings, hashes, lists, and so on. We're just interested in strings so for this we'll have to stringify our data before storing it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Usage &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;We could write two simple functions that will take care of writing and reading data from Redis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;promisify&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;util&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;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="cm"&gt;/* Promisfy so we can have promise base functionality */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getAsync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;promisify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&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;setAsync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;promisify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&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;setexAsync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;promisify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setex&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&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;ttlAsync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;promisify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Writes strigify data to cache
 * @param {string} key key for the cache entry
 * @param {*} value any object/string/number */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/** Retrieves data for a given key
 * @param {string} key key of the cached entry */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheGet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;cacheSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`We just stored: key: **&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;** | value: **&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;**`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can tell Redis to store some data under a specific &lt;strong&gt;key&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: this is not just with discord.js, you can apply the same thing to any Node.js APP or even your own API&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's try it out by storing a new value from our command, a name for example:&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%2Fpkieo5zcpkwvt03y7118.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%2Fpkieo5zcpkwvt03y7118.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can check our Redis instance to be sure that we actually store it. We'll use the built-in &lt;a href="https://redis.io/topics/rediscli" rel="noopener noreferrer"&gt;redis-cli&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run &lt;code&gt;redis-cli&lt;/code&gt; and we'll get something like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  /data&amp;gt; redis-cli
  127.0.0.1:6379&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;KEYS *&lt;/code&gt; to recive all our stored keys
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  127.0.0.1:6379&amp;gt; KEYS &lt;span class="k"&gt;*&lt;/span&gt;
  1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"username"&lt;/span&gt;
  127.0.0.1:6379&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;GET username&lt;/code&gt; to retrieve our stored value
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  127.0.0.1:6379&amp;gt; GET username
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;en3sis&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  127.0.0.1:6379&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what we expected to happen. Now we can get to the fun part and unlock all the potential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache data from APIs &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;For this demo, we'll use a free &lt;a href="https://goweather.herokuapp.com/weather/nuremberg" rel="noopener noreferrer"&gt;Weather API&lt;/a&gt;. Later in the article, we'll explore some real-world examples where this approach shines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preparation &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;We'll install &lt;a href="https://github.com/axios/axios" rel="noopener noreferrer"&gt;Axios HTTP&lt;/a&gt; client to fetch the API (you can use whatever else. &lt;code&gt;npm install axios&lt;/code&gt; ) and create a function that will allow us to fetch the API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Fetch from the Weather API endpoint
 * @param {string} city - City to be fetched
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&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="s2"&gt;`https://goweather.herokuapp.com/weather/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;city&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll change our command to grab the data from the API and send some of the stats to the chat.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// New code addition&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="c1"&gt;// New code addition&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentWeather&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Weather in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&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="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;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; 🌡 Temp:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`**&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currentWeather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperature&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="na"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="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;🍃  Wind:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`**&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currentWeather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wind&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="na"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x03a9f4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we run the command we'll get the following result:&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%2Fdmpo77cone44r1kjcckg.gif" 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%2Fdmpo77cone44r1kjcckg.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sometimes I miss going back to Spain, you can tell why based on the weather :P&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Solving the problem &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Let's imagine that our API has a rate limit of 1000 requests per month. With the current implementation, we could only serve 1k requests and not a single extra one.&lt;/p&gt;

&lt;p&gt;Now imagine our Bot is part of multiple Guilds and multiple users are using our new command. If &lt;strong&gt;user 1&lt;/strong&gt; fetches the data for &lt;code&gt;Almería&lt;/code&gt;, a beautiful city located in the southeast of Spain on the Mediterranean Sea, we could store this data for 1h for example. We don't really need fresh data (&lt;strong&gt;every 10 min&lt;/strong&gt;, and few paid APIs allow you to do so).&lt;br&gt;&lt;br&gt;
Now, when &lt;strong&gt;user 2&lt;/strong&gt; in another server also wants to see the weather in Almería, we'll fetch the data from our local, in memory and blazing fast (~1ms response time) Redis cache.&lt;br&gt;
For the following hour we could show the weather in Almería for &lt;strong&gt;1 billion users&lt;/strong&gt; and we only spent &lt;strong&gt;one single HTPP request&lt;/strong&gt;!&lt;/p&gt;
&lt;h4&gt;
  
  
  Implementation &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;You notice that I mentioned the persistence of the data, another great build-in function that Redis has is TTL (time to live) where you can specify for how long you want some data to be cached, without having to worry about cronjobs, re-validation, and so on.&lt;br&gt;
We'll add a new function that will cache some data for the amount of time we indicated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Writes strigify data to cache
 * @param {string} key key for the cache entry
 * @param {*} value any object/string/number
 * @param {number} ttl cache duration in seconds, default 3600 (1h) */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cacheSetTTL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ttl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setexAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can refactor our code so that every time we want to retrieve the weather from a given City, we first check the cache. If the city is in the cache, we use that data. If it's not in the cache, we'll fetch the data from the API and save the copy in our Redis instance. We can implement this directly in our &lt;code&gt;fetchData()&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Fetch for the Weather API endpoint
 * @param {string} city - City to be fetched
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isCached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;cacheGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;city&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;isCached&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;⚡️  From cache&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;isCached&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Fetch data&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&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;axios&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="s2"&gt;`https://goweather.herokuapp.com/weather/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Save data to cache&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;cacheSetTTL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The whole code can be found &lt;a href="https://github.com/en3sis/discord-guides/blob/main/examples/cache.js" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And we're done! We can now run our command, check for the weather in a given city and return the already cached data or fetch and store it.&lt;/p&gt;

&lt;p&gt;When we run our command, it will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check for the KEY in Redis&lt;/li&gt;
&lt;li&gt;It won't find it so it will make the HTTP request to the API&lt;/li&gt;
&lt;li&gt;Saves the data in Redis using the city as KEY&lt;/li&gt;
&lt;li&gt;Return the data from our &lt;code&gt;fetchData()&lt;/code&gt; function and send the embed&lt;/li&gt;
&lt;/ol&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%2Ftcz2gvi9tfnf8vhdlyoj.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%2Ftcz2gvi9tfnf8vhdlyoj.png" alt="Diagram of workflow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For the second time we (or another user) use the command, it will grab the data directly from the cache.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# In Discord&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cache nuremberg
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cache nuremberg
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cache nuremberg

&lt;span class="c"&gt;# We should see in our application a console log saying:&lt;/span&gt;
Logged &lt;span class="k"&gt;in &lt;/span&gt;as Hans!
⚡️  From cache
⚡️  From cache

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

&lt;/div&gt;



&lt;p&gt;For the first command, we fetch and store the data, for the following commands we serve the data from the cache.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: You can always check the remaining time left by using the redis-cli or the &lt;code&gt;ttlAsync&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;127.0.0.1:6379&amp;gt; KEYS &lt;span class="k"&gt;*&lt;/span&gt;
1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"nuremberg"&lt;/span&gt;
127.0.0.1:6379&amp;gt; TTL nuremberg
&lt;span class="o"&gt;(&lt;/span&gt;integer&lt;span class="o"&gt;)&lt;/span&gt; 3370 &lt;span class="c"&gt;# remining time in seconds&lt;/span&gt;
127.0.0.1:6379&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I hope that this walkthrough helped you get a better understanding and given you some ideas on how to handle the sometimes annoying rate limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-world usecases &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;As promised before, here some examples of when this is really useful.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When dealing as in our example with APIs like the weather where we want to reuse the most amount of data with spending a single request.&lt;/li&gt;
&lt;li&gt;Games APIs: I used it to fetch data from games like Battlefield and reuse the data for things like players' comparison. If I want to see &lt;code&gt;user A&lt;/code&gt; stats, then &lt;code&gt;user B&lt;/code&gt; used the command to see him and I decide to compare our profiles, see how's doing better I can run the command with something like &lt;code&gt;!bf userA userB&lt;/code&gt; and instead of doing two requests to the API to get each player stats, I used the data already available in my cache.&lt;/li&gt;
&lt;li&gt;Same as before, one of the commands is the COVID-19 stats. I also cache the data for a given country (since it's updated once per day) so I can reuse the cache data when another user from a different server fetches the data from the same country.&lt;/li&gt;
&lt;li&gt;Dashboard and Discord API: Discord only allows you to fetch the API by sending an `x ammount of requests per second. While working with a Dashboard where you need to fetch the Guild channels, users, roles... you don't want to do it every time you load a Guild dashboard. For it, I only do it once and set a TTL of ~2mins for some parameters.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Finally
&lt;/h3&gt;

&lt;p&gt;As always, you can find the code with all examples at &lt;a href="https://github.com/en3sis/discord-guides" rel="noopener noreferrer"&gt;https://github.com/en3sis/discord-guides&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any feedback, questions, or suggestions are welcome!&lt;br&gt;
Thanks for reading! ~ &lt;a href="https://twitter.com/en3sis" rel="noopener noreferrer"&gt;https://twitter.com/en3sis&lt;/a&gt;&lt;/p&gt;

</description>
      <category>redis</category>
      <category>discordjs</category>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Advanced Discord.js: Custom embeds using attachments</title>
      <dc:creator>Sorin Curescu</dc:creator>
      <pubDate>Sun, 17 Jan 2021 22:07:24 +0000</pubDate>
      <link>https://dev.to/en3sis/advanced-discord-js-custom-embeds-using-attachments-2bpn</link>
      <guid>https://dev.to/en3sis/advanced-discord-js-custom-embeds-using-attachments-2bpn</guid>
      <description>&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Using SVGs&lt;/li&gt;
&lt;li&gt;Using HTML &amp;amp; CSS&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Looking for a Discord.JS bot? &lt;a href="https://github.com/en3sis/hans" rel="noopener noreferrer"&gt;Check out Hans&lt;/a&gt;! Now it's open-sourced 🥳!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/en3sis/hans" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fvac%2Fimage%2Fupload%2Fv1683578541%2FHans%2Fhans-banner-pattern.png"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;While dealing with sending messages with our Discord Bot, we can do it in multiple ways such as text, attachments and the most common way, &lt;a href="https://discordjs.guide/popular-topics/embeds.html" rel="noopener noreferrer"&gt;embeds&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One example could be something like the following command that will display the weather based on a location:  &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%2Fi%2F97yo0jmjnl0lwzvt00c5.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%2Fi%2F97yo0jmjnl0lwzvt00c5.png" alt="MessageEmbed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sometimes using the &lt;a href="https://discord.js.org/#/docs/main/stable/class/MessageEmbed" rel="noopener noreferrer"&gt;MessageEmbed&lt;/a&gt; class it is enough, but we may want to represent the data  in a different layout/design:   &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%2Fi%2F5uflvzp7wwk3x58stwc1.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%2Fi%2F5uflvzp7wwk3x58stwc1.png" alt="Custom Image Embed - Discord Js"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was seeing several others bots making use of custom ways to show things like stats, users profiles, etc., but I wasn't sure how that can be done and I couldn't find any examples. Some ideas came to my mind and it was time to try things out :P&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The following examples are built on top of &lt;a href="https://discord.js.org/#/" rel="noopener noreferrer"&gt;Discord.js&lt;/a&gt;, a Node.JS wrapper to interact with the Discord API. For more information related to Discord.js and how you can start building your own BOT, you can follow along with this &lt;a href="https://www.youtube.com/watch?v=7A-bnPlxj4k&amp;amp;list=PLRqwX-V7Uu6avBYxeBSwF48YhAnSn_sA4" rel="noopener noreferrer"&gt;amazing series created by Daniel Shiffman&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Using SVG &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;When I first started looking into this, it came to my mind that having an SVG-to-PNG library could work pretty well since we can have the SVG in a template string and replace the placeholders with our data (user input, API calls, etc...) and then make use of &lt;a href="https://discord.js.org/#/docs/main/stable/class/MessageAttachment" rel="noopener noreferrer"&gt;MessageAttachment&lt;/a&gt; to attach the output.&lt;/p&gt;

&lt;p&gt;For the following SVG image,&lt;br&gt;&lt;br&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%2Fi%2F7aiuxtbhd5f2g1qctdm1.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%2Fi%2F7aiuxtbhd5f2g1qctdm1.png" alt="svg_template"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was able to find a library that would take an SVG as a string value and return a &lt;a href="https://nodejs.org/api/buffer.html" rel="noopener noreferrer"&gt;buffer&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 install svg-png-converter

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

&lt;/div&gt;

&lt;p&gt;and then we could have a function to handle all the functionality, in this case we'll have it in its own file and we call it svgToPng.js (Discord only allows attachments of images as JPEG/PNG formats):&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;// svgToPng.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MessageAttachment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discord.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;svg2png&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svg-png-converter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;outputBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;svg2png&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;svg xmlns="http://www.w3.org/2000/svg" width="350" height="136" viewBox="0 0 350 136"&amp;gt;
  &amp;lt;g id="template" transform="translate(-208 -209)"&amp;gt;
    &amp;lt;rect id="background" width="350" height="136" transform="translate(208 209)" fill="#232323"/&amp;gt;
    &amp;lt;text id="_usr_" data-name="&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" transform="translate(326 286)" fill="#fff" font-size="20" font-family="SegoeUI, Segoe UI"&amp;gt;&amp;lt;tspan x="0" y="0"&amp;gt;Hello &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/tspan&amp;gt;&amp;lt;/text&amp;gt;
    &amp;lt;path id="icon" d="M7.5-16.68,15-13.32v5a10.351,10.351,0,0,1-2.148,6.348A9.33,9.33,0,0,1,7.5,1.68,9.33,9.33,0,0,1,2.148-1.973,10.351,10.351,0,0,1,0-8.32v-5Zm1.758,4A2.435,2.435,0,0,0,7.5-13.4a2.435,2.435,0,0,0-1.758.723A2.361,2.361,0,0,0,5-10.918a2.425,2.425,0,0,0,.742,1.777A2.4,2.4,0,0,0,7.5-8.4a2.4,2.4,0,0,0,1.758-.742A2.425,2.425,0,0,0,10-10.918,2.361,2.361,0,0,0,9.258-12.676ZM7.5-6.836a8.754,8.754,0,0,0-2.031.273,6.19,6.19,0,0,0-2.051.9A1.74,1.74,0,0,0,2.5-4.258,6.007,6.007,0,0,0,4.707-2.383,5.947,5.947,0,0,0,7.5-1.6a5.947,5.947,0,0,0,2.793-.781A6.007,6.007,0,0,0,12.5-4.258a1.486,1.486,0,0,0-.547-1.094,4.2,4.2,0,0,0-1.348-.82A10.513,10.513,0,0,0,8.984-6.66,7.147,7.147,0,0,0,7.5-6.836Z" transform="translate(302 286)" fill="#fff"/&amp;gt;
  &amp;lt;/g&amp;gt;
&amp;lt;/svg&amp;gt;
`&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;buffer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;png&lt;/span&gt;&lt;span class="dl"&gt;'&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="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;// for more configuration options refer to the library&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`This is a test:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MessageAttachment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outputBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${name}.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


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

&lt;/div&gt;
&lt;h4&gt;
  
  
  The result:
&lt;/h4&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%2Fi%2Fs8witq0nwa8mieykf5z4.gif" 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%2Fi%2Fs8witq0nwa8mieykf5z4.gif" alt="SVG Example - GIF"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This was working great with small illustrations where there are just some small changes in the text. For other things, it was not good enough (when you want to dynamically show an image based on some value, such as a list of different icons for each different user role). While this still could be achieved with the SVG, it felt that it's too much work and it wasn't the right fit.&lt;/p&gt;

&lt;p&gt;It was time to go back to the drawing board and to think about some other ways to achieve this. &lt;/p&gt;
&lt;h2&gt;
  
  
  Using HTML &amp;amp; CSS &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In the same way, we deal with SVG to PNG conversion, it would be possible to render a webpage with all our data, assets and take a screenshot of it (using puppeteer or some other libraries) and finally attach the screenshot? This would solve many issues and creating a layout in the front-end it's something that I understand and I feel comfortable with, so it sounded like a great idea!&lt;/p&gt;

&lt;p&gt;It ends up that you can! After a quick search in the infinitely large npm registry and I was able to find a library that does exactly that (&lt;a href="https://www.npmjs.com/package/node-html-to-image" rel="noopener noreferrer"&gt;node-html-to-image&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Now the only thing we have to do is to build the layout for our example (HTML &amp;amp; CSS) and to put everything together. We'll make use of the &lt;a href="https://avatars.dicebear.com/" rel="noopener noreferrer"&gt;avatar API&lt;/a&gt; to dynamically generate an avatar based on the user's name input.&lt;/p&gt;

&lt;p&gt;Let start building a quick mockup for our card:&lt;br&gt;
 &lt;iframe src="https://codesandbox.io/embed/embedexample-8y0ku?initialpath=/initial/load/path"&gt;
&lt;/iframe&gt;
 &lt;/p&gt;

&lt;p&gt;Now that we have our HTML &amp;amp; CSS, we can start working on generating an image from it using the NPM package: &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 install node-html-to-image

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

&lt;/div&gt;

&lt;p&gt;and in our htmlToPng.js file we have:&lt;/p&gt;


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

&lt;p&gt;&lt;span class="c1"&gt;//  htmlToPng.js&lt;/span&gt;&lt;br&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MessageAttachment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;discord.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;br&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nodeHtmlToImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-html-to-image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;_htmlTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;!DOCTYPE html&amp;gt;&lt;br&gt;
&amp;lt;html lang="en"&amp;gt;&lt;br&gt;
  &amp;lt;head&amp;gt;&lt;br&gt;
    &amp;lt;meta charset="UTF-8" /&amp;gt;&lt;br&gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;&lt;br&gt;
    &amp;lt;meta http-equiv="X-UA-Compatible" content="ie=edge" /&amp;gt;&lt;br&gt;
    &amp;lt;style&amp;gt;&lt;br&gt;
      body {&lt;br&gt;
        font-family: "Poppins", Arial, Helvetica, sans-serif;&lt;br&gt;
        background: rgb(22, 22, 22);&lt;br&gt;
        color: #fff;&lt;br&gt;
        max-width: 300px;&lt;br&gt;
      }&lt;/span&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  .app {
    max-width: 300px;
    padding: 20px;
    display: flex;
    flex-direction: row;
    border-top: 3px solid rgb(16, 180, 209);
    background: rgb(31, 31, 31);
    align-items: center;
  }

  img {
    width: 50px;
    height: 50px;
    margin-right: 20px;
    border-radius: 50%;
    border: 1px solid #fff;
    padding: 5px;
  }
&amp;amp;lt;/style&amp;amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&amp;lt;/head&amp;gt;&lt;br&gt;
  &amp;lt;body&amp;gt;&lt;br&gt;
    &amp;lt;div class="app"&amp;gt;&lt;br&gt;
      &amp;lt;img src="&lt;a href="https://avatars.dicebear.com/4.5/api/avataaars/" rel="noopener noreferrer"&gt;https://avatars.dicebear.com/4.5/api/avataaars/&lt;/a&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.svg" /&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;amp;lt;h4&amp;amp;gt;Welcome &amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;${&amp;lt;/span&amp;gt;&amp;lt;span class="nx"&amp;gt;name&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span class="s2"&amp;gt;&amp;amp;lt;/h4&amp;amp;gt;
&amp;amp;lt;/div&amp;amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&amp;lt;/body&amp;gt;&lt;br&gt;
&amp;lt;/html&amp;gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;nodeHtmlToImage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;_htmlTemplate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jpeg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
    &lt;span class="na"&gt;puppeteerArgs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&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;br&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;&lt;br&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;buffer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;&lt;br&gt;
&lt;span class="c1"&gt;// for more configuration options refer to the library&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MessageAttachment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&lt;code&gt;&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;${&amp;lt;/span&amp;gt;&amp;lt;span class="nx"&amp;gt;name&amp;lt;/span&amp;gt;&amp;lt;span class="p"&amp;gt;}&amp;lt;/span&amp;gt;&amp;lt;span class="s2"&amp;gt;.jpeg&lt;/code&gt;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

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

&lt;/div&gt;
&lt;h4&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  The result:&lt;br&gt;
&lt;/h4&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%2Fi%2Fm4bv874er90f3by3rxty.gif" 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%2Fi%2Fm4bv874er90f3by3rxty.gif" alt="HTML Example - GIF"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Now we have two new ways to generate more complex and good looking cards for your Discord users to consume. One good example of this would be to display some game stats for example. My brother and his friends play World of Warcraft, so I took a little time to create a command, that will display the main stats for their characters using the HTML &amp;amp; CSS technique, here is the result of it:&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%2Fi%2F32oj0l49toik9d7y015d.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%2Fi%2F32oj0l49toik9d7y015d.png" alt="WOW Stats - Discord.js"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would suggest using the SVG method while dealing with a static design/elements but only some text changes and use the HTML one while dealing with dynamic lists or images.&lt;/p&gt;

&lt;p&gt;In the previous example, we might be able to archive the same result using an SVG but I find it hard since each element (the item icon and his value) are coming from an API in a different format (JPG/PNG for the images). Appending the item to a list it's way easier than applying a block of code to the SVG bases on some condition. Also styling things like images wrappers (round images with round borders) it's way easier in HTML &amp;amp; CSS than with SVG using things like masks.&lt;/p&gt;

&lt;p&gt;I hope that this guide will open new ways for your creativity, which I would love to see them. &lt;/p&gt;

&lt;p&gt;You can find the BOT code with all examples at &lt;a href="https://github.com/en3sis/discord-guides" rel="noopener noreferrer"&gt;https://github.com/en3sis/discord-guides&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Any feedback, questions or suggestions are welcome! &lt;br&gt;
Thanks for reading! ~ &lt;a href="https://twitter.com/en3sis" rel="noopener noreferrer"&gt;https://twitter.com/en3sis&lt;/a&gt;&lt;/p&gt;

</description>
      <category>discordjs</category>
      <category>node</category>
      <category>embed</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
