<?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.us-east-2.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>Terminal autocomplete on macOS: an IDE-style popup for zsh, bash, and fish</title>
      <dc:creator>Sorin Curescu</dc:creator>
      <pubDate>Wed, 14 Oct 2026 19:50:30 +0000</pubDate>
      <link>https://dev.to/en3sis/terminal-autocomplete-on-macos-an-ide-style-popup-for-zsh-bash-and-fish-1boi</link>
      <guid>https://dev.to/en3sis/terminal-autocomplete-on-macos-an-ide-style-popup-for-zsh-bash-and-fish-1boi</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; &lt;a href="https://figxit.com" rel="noopener noreferrer"&gt;Figxit&lt;/a&gt; is a free, open source autocomplete popup for the macOS terminal. It lists subcommands, options, &lt;code&gt;package.json&lt;/code&gt; scripts, and Makefile targets as you type, in zsh, bash, and fish, and orders them by what you run most in the current folder. It runs locally with no account and no telemetry. Source: &lt;a href="https://github.com/en3sis/figxit" rel="noopener noreferrer"&gt;github.com/en3sis/figxit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn60jorciz3p7yy7510rg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn60jorciz3p7yy7510rg.gif" alt="Figxit popup completing git checkout with a branch name, then Tab inserts it" width="719" height="405"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/en3sis" rel="noopener noreferrer"&gt;
        en3sis
      &lt;/a&gt; / &lt;a href="https://github.com/en3sis/figxit" rel="noopener noreferrer"&gt;
        figxit
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Native autocomplete popup for the macOS terminal. The completion part of Fig, rebuilt as a small local app. No account, no AI, no telemetry.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/en3sis/figxit/assets/icon.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fen3sis%2Ffigxit%2FHEAD%2Fassets%2Ficon.png" width="128" alt="Figxit app icon"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Figxit&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;IDE-style autocomplete for the macOS terminal, for zsh, bash, and fish. Figxit completes commands, subcommands, options, &lt;code&gt;package.json&lt;/code&gt; scripts, and Makefile targets as you type, ordered by what you use most in the current project.&lt;/p&gt;
&lt;p&gt;It is a free, open source replacement for Fig's autocomplete. It runs locally, with no account, no AI chat, and no telemetry.&lt;/p&gt;

  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fen3sis%2Ffigxit%2FHEAD%2Fassets%2Fmake.png" width="640" alt="Terminal window with make typed and the popup listing Makefile targets with their section and help text"&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why it exists&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Fig added autocomplete to the terminal: you typed &lt;code&gt;git &lt;/code&gt; and got the subcommands, each with a short description. Fig joined AWS in 2023 and the app was shut down in September 2024. Its autocomplete now ships inside Kiro CLI, a closed-source AI agent tool.&lt;/p&gt;
&lt;p&gt;The open alternatives each lack a part of what Fig did:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Shell completion menus open only when you press Tab, and they do not know which command you use most.&lt;/li&gt;
&lt;li&gt;Tools that draw the list inside the terminal wrap the shell session to do it.&lt;/li&gt;
&lt;li&gt;History…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/en3sis/figxit" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;What terminal autocomplete is&lt;/li&gt;
&lt;li&gt;Options on macOS today&lt;/li&gt;
&lt;li&gt;What Figxit does&lt;/li&gt;
&lt;li&gt;How it works&lt;/li&gt;
&lt;li&gt;Install&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What terminal autocomplete is
&lt;/h2&gt;

&lt;p&gt;Terminal autocomplete suggests the next word of a command while you type it: a subcommand, a flag, a file, a branch, a script name. There are three common forms:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Form&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;When it shows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tab completion&lt;/td&gt;
&lt;td&gt;zsh &lt;code&gt;compinit&lt;/code&gt;, bash-completion&lt;/td&gt;
&lt;td&gt;Only after you press Tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inline suggestion&lt;/td&gt;
&lt;td&gt;zsh-autosuggestions, fish&lt;/td&gt;
&lt;td&gt;As you type, one full past command in grey&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Popup list&lt;/td&gt;
&lt;td&gt;Fig (discontinued), Figxit&lt;/td&gt;
&lt;td&gt;As you type, a list of options with a description on each row&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tab completion knows what a command accepts, but you have to ask for it. Inline suggestions are fast, but they only show one line from your history. A popup list shows every valid next word with a short description, like autocomplete in a code editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Options on macOS today
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fig&lt;/strong&gt; added popup autocomplete to the terminal. It joined AWS in 2023 and the app was shut down in September 2024. Its autocomplete now ships inside Kiro CLI, a closed-source AI agent tool that needs an account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shell completion menus&lt;/strong&gt; (zsh, bash-completion, fzf-tab) open only on Tab and do not rank by your usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;inshellisense&lt;/strong&gt; (Microsoft) uses the same Fig specs and draws the list inside the terminal session. It is cross-platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;History tools&lt;/strong&gt; such as Atuin and fzf find a full past command, but they do not list what a command can do.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Figxit combines the three missing parts: a list that opens while you type, completion data for 700+ tools, and ranking from your own history.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Figxit does
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Subcommands and options for 700+ tools
&lt;/h3&gt;

&lt;p&gt;Figxit loads the open source &lt;a href="https://github.com/withfig/autocomplete" rel="noopener noreferrer"&gt;Fig completion specs&lt;/a&gt; (git, docker, npm, kubectl, and more). Type a command and a space for its subcommands, or a dash for its options. Options already on the line are hidden.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7hfwxeqlekcfwc02w6t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm7hfwxeqlekcfwc02w6t.png" alt="git ch typed in the terminal, popup listing matching git subcommands with descriptions" width="797" height="107"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi8qxys0ja35uw8ullwcw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi8qxys0ja35uw8ullwcw.png" alt="docker run -- typed, popup listing docker run options with descriptions" width="799" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Specs also supply live values: git branches after &lt;code&gt;git checkout&lt;/code&gt;, hosts after &lt;code&gt;ssh&lt;/code&gt;, files and folders where a command takes a path.&lt;/p&gt;

&lt;h3&gt;
  
  
  npm, pnpm, yarn, and bun scripts
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;npm run&lt;/code&gt;, &lt;code&gt;pnpm&lt;/code&gt;, &lt;code&gt;yarn&lt;/code&gt;, and &lt;code&gt;bun run&lt;/code&gt; list the scripts in &lt;code&gt;package.json&lt;/code&gt;, with the command each one runs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg176isw727p0e1gvuqa8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg176isw727p0e1gvuqa8.png" alt="npm run typed, popup listing package.json scripts and the command for each" width="545" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Makefile targets
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;make&lt;/code&gt; lists the targets of the &lt;code&gt;Makefile&lt;/code&gt; in the current folder. The right column shows help comments that many projects already write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;##@ Development
&lt;/span&gt;&lt;span class="nl"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; Start the stack with hot reload&lt;/span&gt;
&lt;span class="nl"&gt;dev-down&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; Stop the stack&lt;/span&gt;

&lt;span class="c"&gt;##@ Tests
&lt;/span&gt;&lt;span class="nl"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; Run the unit tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;##@&lt;/code&gt; starts a section, and &lt;code&gt;##&lt;/code&gt; after a target is its help text. Both are optional.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foos3eoxd3fmav13lele0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foos3eoxd3fmav13lele0.png" alt="make typed, popup listing Makefile targets with section and help text" width="727" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ranking from your history
&lt;/h3&gt;

&lt;p&gt;Figxit reads your &lt;a href="https://atuin.sh" rel="noopener noreferrer"&gt;Atuin&lt;/a&gt; history database, read-only. A word scores higher when you used it in the same folder, in the same repository, or recently. Without Atuin, the popup still works in the order the sources give.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flkhboohxkw892ppmdzay.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flkhboohxkw892ppmdzay.png" alt="p typed, popup listing commands that start with p, with product icons" width="520" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Red icon for production
&lt;/h3&gt;

&lt;p&gt;A row that touches production gets a red icon, so you notice it before you press Enter. A row gets it when the name is in capitals (&lt;code&gt;DEPLOY&lt;/code&gt;), contains &lt;code&gt;prod&lt;/code&gt; or &lt;code&gt;production&lt;/code&gt; (&lt;code&gt;deploy:prod&lt;/code&gt;, &lt;code&gt;api-prod&lt;/code&gt;), or is a Makefile target tagged &lt;code&gt;## [prod]&lt;/code&gt;. Enter inserts a red row without running it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxt2io8twj2x83r1mze9u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxt2io8twj2x83r1mze9u.png" alt="Popup listing Makefile targets, with db-reset, deploy-prod, and release marked with red icons" width="728" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Keys
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Up, Down&lt;/td&gt;
&lt;td&gt;Move the highlight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tab&lt;/td&gt;
&lt;td&gt;Insert the highlighted row&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enter&lt;/td&gt;
&lt;td&gt;Insert the row and run the line (a red row is inserted, not run)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Esc&lt;/td&gt;
&lt;td&gt;Close the popup&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When the popup is closed, keys keep their normal behaviour: Tab opens your usual completion, and Up searches history.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Figxit has three parts that talk over unix sockets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; shell adapter  ── buffer, cursor, folder ──▶  engine  ── rows, position ──▶  helper
 (zsh, bash, fish)                              (Bun)                         (Swift, AppKit)
      ▲                                           │
      └───────── text to insert on Tab ───────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shell adapter:&lt;/strong&gt; a line editor hook sends the buffer to the engine after each key, without waiting for a reply. The shell only waits (300 ms at most) when you press Tab or Enter with the list open.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engine:&lt;/strong&gt; a Bun process that keeps your history and the specs in memory. It parses the line, collects candidates from project files, specs, and history, then ranks them. Values that need a command, like git branches, arrive in a second pass so the first rows are never delayed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Helper:&lt;/strong&gt; a small macOS app that draws a floating panel at the cursor. It does not take keyboard focus and needs no Accessibility or Screen Recording permission. In tmux, tmux reports the cursor cell; outside tmux, the terminal does.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;Requirements: Apple Silicon Mac, macOS 13 or later, and zsh, bash 5+, or fish 4. Tested in Ghostty, iTerm2, Terminal, and any terminal running tmux.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download &lt;a href="https://figxit.com/download/Figxit.dmg" rel="noopener noreferrer"&gt;Figxit.dmg&lt;/a&gt; and drag Figxit to Applications.&lt;/li&gt;
&lt;li&gt;Open Figxit. A setup window shows the line for your shell. Click &lt;strong&gt;Add&lt;/strong&gt;, or add it yourself:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# zsh, in ~/.zshrc&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="s1"&gt;'/Applications/Figxit.app/Contents/MacOS/figxit-engine'&lt;/span&gt; init zsh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# bash, in ~/.bashrc&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="s1"&gt;'/Applications/Figxit.app/Contents/MacOS/figxit-engine'&lt;/span&gt; init bash&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# fish, in ~/.config/fish/conf.d/figxit.fish&lt;/span&gt;
status is-interactive&lt;span class="p"&gt;;&lt;/span&gt; and &lt;span class="s1"&gt;'/Applications/Figxit.app/Contents/MacOS/figxit-engine'&lt;/span&gt; init fish | &lt;span class="nb"&gt;source&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a new terminal and start typing 🥳&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;figxit doctor&lt;/code&gt; to check each part of the installation.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Is Figxit a replacement for Fig autocomplete?&lt;/strong&gt;&lt;br&gt;
Yes, for the autocomplete popup. It uses the same open source completion specs. It does not include Fig's other features, such as dotfile sync, and it has no AI chat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use it in place of Amazon Q or Kiro CLI autocomplete?&lt;/strong&gt;&lt;br&gt;
Yes, if you only want autocomplete. Figxit needs no account. It is not affiliated with Fig, Amazon, or Kiro.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How is it different from inshellisense?&lt;/strong&gt;&lt;br&gt;
Both use the Fig specs. inshellisense draws inside the terminal session and runs on Windows and Linux. Figxit shows a native macOS window and ranks rows by your history. It is macOS only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it work in the VS Code terminal?&lt;/strong&gt;&lt;br&gt;
No. Terminals embedded in an editor window do not report where the panel is. Use a terminal app, with or without tmux.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it send what I type anywhere?&lt;/strong&gt;&lt;br&gt;
No. There is no telemetry and no account. The only network request is a daily update check, which you can turn off in Settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it free?&lt;/strong&gt;&lt;br&gt;
Yes. MIT licence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Website and demo video: &lt;a href="https://figxit.com" rel="noopener noreferrer"&gt;figxit.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source, docs, and issues: &lt;a href="https://github.com/en3sis/figxit" rel="noopener noreferrer"&gt;github.com/en3sis/figxit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Download: &lt;a href="https://figxit.com/download/Figxit.dmg" rel="noopener noreferrer"&gt;Figxit.dmg&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it, issues and feature requests are welcome on &lt;a href="https://github.com/en3sis/figxit/issues" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>terminal</category>
      <category>macos</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <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://media2.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" width="662" height="301"&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpkieo5zcpkwvt03y7118.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpkieo5zcpkwvt03y7118.png" alt="Alt Text" width="800" height="306"&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdmpo77cone44r1kjcckg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdmpo77cone44r1kjcckg.gif" alt="Alt Text" width="720" height="344"&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftcz2gvi9tfnf8vhdlyoj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftcz2gvi9tfnf8vhdlyoj.png" alt="Diagram of workflow" width="800" height="500"&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fef6c6w6jxa9irfi82aa6.png" width="662" height="301"&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://media2.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://media2.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" width="514" height="248"&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://media2.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://media2.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" width="491" height="310"&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://media2.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://media2.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" width="350" height="136"&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;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install svg-png-converter&lt;/code&gt;&lt;br&gt;
&lt;/p&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;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;// 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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs8witq0nwa8mieykf5z4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fs8witq0nwa8mieykf5z4.gif" alt="SVG Example - GIF" width="600" height="331"&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" width="100%" height="500"&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;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install node-html-to-image&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;and in our htmlToPng.js file we have:&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;//  htmlToPng.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="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;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;_htmlTemplate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
    &amp;lt;meta http-equiv="X-UA-Compatible" content="ie=edge" /&amp;gt;
    &amp;lt;style&amp;gt;
      body {
        font-family: "Poppins", Arial, Helvetica, sans-serif;
        background: rgb(22, 22, 22);
        color: #fff;
        max-width: 300px;
      }

      .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;lt;/style&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div class="app"&amp;gt;
      &amp;lt;img src="https://avatars.dicebear.com/4.5/api/avataaars/&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;.svg" /&amp;gt;

      &amp;lt;h4&amp;gt;Welcome &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;/h4&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
`&lt;/span&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;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;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;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;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;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="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="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="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;/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;.jpeg`&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://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm4bv874er90f3by3rxty.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fm4bv874er90f3by3rxty.gif" alt="HTML Example - GIF" width="600" height="331"&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://media2.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://media2.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" width="799" height="696"&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>
