<?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>Forem: Siddhant Kumar</title>
    <description>The latest articles on Forem by Siddhant Kumar (@siddhantk232).</description>
    <link>https://forem.com/siddhantk232</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%2F199325%2Ff9d9ce90-d583-4d4c-a34f-c57d8d51ba8d.jpg</url>
      <title>Forem: Siddhant Kumar</title>
      <link>https://forem.com/siddhantk232</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/siddhantk232"/>
    <language>en</language>
    <item>
      <title>Malai - Share your dev server (and more) over P2P</title>
      <dc:creator>Siddhant Kumar</dc:creator>
      <pubDate>Mon, 05 May 2025 12:05:57 +0000</pubDate>
      <link>https://forem.com/siddhantk232/malai-share-your-dev-server-and-more-over-p2p-12bf</link>
      <guid>https://forem.com/siddhantk232/malai-share-your-dev-server-and-more-over-p2p-12bf</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/kulfi-project/kulfi" rel="noopener noreferrer"&gt;&lt;code&gt;malai&lt;/code&gt;&lt;/a&gt; is a new open-source tool from the team at &lt;a href="https://fifthtry.com/team/" rel="noopener noreferrer"&gt;FifthTry&lt;/a&gt;. It helps you share your local HTTP server with the world — instantly and securely.&lt;/p&gt;

&lt;p&gt;Built on top of the powerful &lt;a href="https://iroh.computer" rel="noopener noreferrer"&gt;iroh&lt;/a&gt; P2P stack, &lt;code&gt;malai&lt;/code&gt; lets you expose your local development environment without deploying it to a public server or configuring firewalls and DNS.&lt;/p&gt;

&lt;p&gt;Whether you're testing webhooks, giving someone a quick demo, or just want to show off your side project, &lt;code&gt;malai&lt;/code&gt; makes it dead simple.&lt;/p&gt;

&lt;p&gt;Install &lt;code&gt;malai&lt;/code&gt; today using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://malai.sh/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run:&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="nv"&gt;$ &lt;/span&gt;malai http 3000 &lt;span class="nt"&gt;--public&lt;/span&gt;
Malai: Sharing http://127.0.0.1:3000 at
https://pubqaksutn9im0ncln2bki3i8diekh3sr4vp94o2cg1agjrb8dhg.kulfi.site
To avoid the public proxy, run your own with: malai http-bridge

Or use: malai browse kulfi://pubqaksutn9im0ncln2bki3i8diekh3sr4vp94o2cg1agjrb8dhg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will share your local HTTP server running on &lt;code&gt;http://localhost:3000&lt;/code&gt; with the world via a secure, shareable URL.&lt;/p&gt;

&lt;p&gt;You can also use &lt;code&gt;malai browse&lt;/code&gt; subcommand to access this from another computer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;malai browse kulfi://&amp;lt;id52-from-the-above-output&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the &lt;a href="https://dev.to/get-started/"&gt;Getting Started guide&lt;/a&gt; or run &lt;code&gt;malai --help&lt;/code&gt; to explore all the options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;p&gt;Here are just a few things you can do with &lt;code&gt;malai&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User Acceptance Testing&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Share your in-progress app with non-technical stakeholders without pushing to staging.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTPS Testing&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test HTTPS-only features like Service Workers and OAuth callbacks with a trusted remote URL.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Webhook Testing&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test Stripe, GitHub, or any other webhook provider locally, without deploying your backend.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Developer Preview&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Send a URL to your teammate to get feedback on your frontend work before merging.&lt;/p&gt;

&lt;p&gt;All this without any config, DNS setup, or cloud deploys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bring your own HTTP bridge
&lt;/h2&gt;

&lt;p&gt;Malai is designed with decentralization in mind. By default, Malai uses a free public HTTP bridge hosted at &lt;code&gt;kulfi.site&lt;/code&gt;. This means you can start sharing your local server with the world right away — no setup required.&lt;/p&gt;

&lt;p&gt;But if you'd prefer more control, privacy, or reliability, you can run your own bridge. Malai makes it easy to self-host your own HTTP bridge, and you can configure the CLI to use your custom bridge instead of the default one.&lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://dev.to/get-started/#run-your-own-bridge"&gt;Getting Started&lt;/a&gt; guide for step-by-step instructions on setting up your own bridge and pointing Malai to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;We're actively working on expanding what you can do with &lt;code&gt;malai&lt;/code&gt;. Here’s a peek at what’s coming:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Share any &lt;strong&gt;TCP server&lt;/strong&gt;, not just HTTP&lt;br&gt;&lt;br&gt;
Soon you'll be able to share any TCP-based service running locally — including your Postgres database, Redis, or even a custom TCP protocol — using the same seamless workflow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Native support for &lt;strong&gt;SSH&lt;/strong&gt; using &lt;code&gt;malai ssh&lt;/code&gt;&lt;br&gt;&lt;br&gt;
We're adding support for secure shell access over P2P with &lt;code&gt;malai ssh&lt;/code&gt;. This will let you remotely access your development machine or share a shell session without needing public IPs or VPNs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Experimental support for sharing &lt;strong&gt;local devices&lt;/strong&gt; like printers and storage over peer-to-peer&lt;br&gt;&lt;br&gt;
We're exploring ways to let you securely share physical hardware — like printers or external drives — directly from your machine over P2P, with fine-grained access controls.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In parallel, we're building a companion GUI app called &lt;strong&gt;Kulfi&lt;/strong&gt;. Kulfi will make it easy to browse and connect to shared services — no bridge required. It'll also include built-in access control (ACL) management, so you can choose exactly who gets access to what. Whether you’re sharing with teammates, friends, or devices across your network, Kulfi will give you visibility and control.&lt;/p&gt;

&lt;p&gt;You can learn more about our plans for &lt;code&gt;kulfi&lt;/code&gt; and &lt;code&gt;malai&lt;/code&gt; on our &lt;a href="https://github.com/kulfi-project/kulfi/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt; page.&lt;/p&gt;

&lt;p&gt;Stay tuned — and if you have ideas, feature requests, or want to contribute, feel free to open an issue or pull request on &lt;a href="https://github.com/kulfi-project/kulfi" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;! You can also join our &lt;a href="https://discord.gg/nK4ZP8HpV7" rel="noopener noreferrer"&gt;discord&lt;/a&gt; and chat about the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Star us on GitHub ⭐
&lt;/h2&gt;

&lt;p&gt;We're just getting started, and your support means a lot.&lt;/p&gt;

&lt;p&gt;If you like what we're building, consider &lt;a href="https://github.com/kulfi-project/kulfi" rel="noopener noreferrer"&gt;starring the repo&lt;/a&gt; on GitHub. It helps others discover the project and keeps us motivated to build more!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>tooling</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Rasoi - An eCommerce store</title>
      <dc:creator>Siddhant Kumar</dc:creator>
      <pubDate>Mon, 03 Jan 2022 15:03:41 +0000</pubDate>
      <link>https://forem.com/siddhantk232/rasoi-an-ecommerce-store-43eo</link>
      <guid>https://forem.com/siddhantk232/rasoi-an-ecommerce-store-43eo</guid>
      <description>&lt;p&gt;A small online store built using nextjs, mongodb, and stripe. The home page is a &lt;a href="https://nextjs.org/docs/basic-features/pages#static-generation-recommended" rel="noopener noreferrer"&gt;generated product listing&lt;/a&gt; that is rebuilt every time there is a change in products collection.&lt;/p&gt;

&lt;p&gt;link to &lt;a href="https://rasoi.vercel.app/" rel="noopener noreferrer"&gt;demo&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;E-commerce creation&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Code
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/siddhantk232" rel="noopener noreferrer"&gt;
        siddhantk232
      &lt;/a&gt; / &lt;a href="https://github.com/siddhantk232/rasoi" rel="noopener noreferrer"&gt;
        rasoi
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Rasoi&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;A small &lt;a href="https://en.wikipedia.org/wiki/Headless_commerce" rel="nofollow noopener noreferrer"&gt;headless&lt;/a&gt; online
store built using nextjs, mongodb, and stripe. The home page is a &lt;a href="https://nextjs.org/docs/basic-features/pages#static-generation-recommended" rel="nofollow noopener noreferrer"&gt;generated
product
listing&lt;/a&gt;
that is rebuilt every time there is a change in products collection.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Uses&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.atlas.mongodb.com/api/data-api/" rel="nofollow noopener noreferrer"&gt;mongo data api&lt;/a&gt; to interact with the mongodb database.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.mongodb.com/realm/triggers/trigger-types/" rel="nofollow noopener noreferrer"&gt;mongo realm triggers&lt;/a&gt; to trigger nextjs redeploy hook.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stripe.com/docs/payments/checkout" rel="nofollow noopener noreferrer"&gt;stripe checkout&lt;/a&gt; for payments.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;DB&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;products collection&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;{
  _id,
  name,
  description,
  images,
  price,
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;creating a products triggers nextjs build SSG&lt;/li&gt;
&lt;li&gt;view products&lt;/li&gt;
&lt;li&gt;order them&lt;/li&gt;
&lt;li&gt;(optional) cart feature&lt;/li&gt;
&lt;li&gt;(optional) cart checkout&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;orders collection&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;{
  _id,
  sessionId,
  email,
  amount,
  items
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;creating an order triggers an email notification&lt;/li&gt;
&lt;li&gt;stripe checkout on client&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/siddhantk232/rasoi" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Uses &lt;a href="https://docs.atlas.mongodb.com/api/data-api/" rel="noopener noreferrer"&gt;mongo data api&lt;/a&gt; to interact with the mongodb database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Uses &lt;a href="https://docs.mongodb.com/realm/triggers/trigger-types/" rel="noopener noreferrer"&gt;mongo realm triggers&lt;/a&gt; to trigger nextjs redeploy hook.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Uses &lt;a href="https://stripe.com/docs/payments/checkout" rel="noopener noreferrer"&gt;stripe checkout&lt;/a&gt; for payments.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>atlashackathon</category>
      <category>mongodb</category>
      <category>javascript</category>
      <category>web</category>
    </item>
    <item>
      <title>Postgres Full Text Search</title>
      <dc:creator>Siddhant Kumar</dc:creator>
      <pubDate>Thu, 22 Apr 2021 00:00:00 +0000</pubDate>
      <link>https://forem.com/siddhantk232/postgres-full-text-search-504a</link>
      <guid>https://forem.com/siddhantk232/postgres-full-text-search-504a</guid>
      <description>&lt;p&gt;Today, I want to talk about how easy it is to implement a full-text search in Postgres. Let's dive right into it!&lt;/p&gt;

&lt;p&gt;But first, why you would want to implement text search when you can do queries with &lt;code&gt;LIKE&lt;/code&gt; or &lt;code&gt;ILIKE&lt;/code&gt;. There are many reasons to this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Full-text search queries against a specialized &lt;a href="https://en.wikipedia.org/wiki/Stemming" rel="noopener noreferrer"&gt;stemmed&lt;/a&gt; version of your &lt;br&gt;
data. This means that it can search for complex English &lt;br&gt;
words like "justifiable" when searching for "justify".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It is fast, especially combined with indexes. LIKE and &lt;br&gt;
ILIKE will have to do a sequential scan of your data to &lt;br&gt;
select the results.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can blacklist certain words from getting parsed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And much more!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Postgres Instance
&lt;/h4&gt;

&lt;p&gt;I am using &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;docker&lt;/a&gt; to create a new Postgres instance on my local machine. To create start a Postgres container, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgresfts &lt;span class="nt"&gt;-p&lt;/span&gt; 5432:5432 &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres &lt;span class="nt"&gt;-d&lt;/span&gt; postgres
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feel free to use any frontend clients you like to connect to this Postgres instance. For this job, &lt;code&gt;psql&lt;/code&gt; will do fine.&lt;/p&gt;

&lt;h4&gt;
  
  
  Sample Data
&lt;/h4&gt;

&lt;p&gt;We need some data to search. For this I am using the example of a books table that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;                               Table &lt;span class="s2"&gt;"public.books"&lt;/span&gt;
   Column    |  Type   | Collation | Nullable |              Default
&lt;span class="nt"&gt;-------------&lt;/span&gt;+---------+-----------+----------+-----------------------------------
 &lt;span class="nb"&gt;id&lt;/span&gt;          | bigint  |           | not null | nextval&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'books_id_seq'&lt;/span&gt;::regclass&lt;span class="o"&gt;)&lt;/span&gt;
 title       | text    |           |          |
 description | text    |           |          |
 price       | numeric |           |          |
Indexes:
    &lt;span class="s2"&gt;"books_pkey"&lt;/span&gt; PRIMARY KEY, btree &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To create this books table, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="nb"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have our table, let's fill in some data to work with. Inserting few rows are fine. But we are doing a full-text search and it is good to have few thousand rows to spice things up 😉.&lt;/p&gt;

&lt;p&gt;For this, I have put together this simple nodejs script:&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;Pool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pg&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;format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pg-format&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;faker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;faker&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;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;172.17.0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// postgres host&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postgres&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;passwd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postgres&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;books&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;books&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;productName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;productDescription&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;faker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;price&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT INTO books (title, description, price) VALUES %L&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;books&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Done!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;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="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above script will create 100k rows using faker.js and insert them in the books table.&lt;/p&gt;

&lt;p&gt;You can also clone this git repo from &lt;a href="https://github.com/siddhantk232/node_postgres_scripts" rel="noopener noreferrer"&gt;siddhantk232/node_postgres_scripts&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Search!
&lt;/h2&gt;

&lt;p&gt;Now that we have our data ready in the books table. Let's implement full-text search.&lt;/p&gt;

&lt;p&gt;In Postgres, you need two things to start with, one is a &lt;code&gt;tsvector&lt;/code&gt; to search and the other is the search query aka the &lt;code&gt;tsquery&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  tsvector?
&lt;/h4&gt;

&lt;p&gt;The tsvector can be another column in your table that stores normalized keywords from the actual data.&lt;/p&gt;

&lt;p&gt;In this case, I want the book's title and description column to be searchable. So I would create a tsvector of these columns.&lt;/p&gt;

&lt;p&gt;Doing that is pretty easy, we can create a generated column of type tsvector.&lt;/p&gt;

&lt;p&gt;To do this, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;fts&lt;/span&gt; &lt;span class="n"&gt;tsvector&lt;/span&gt; 
  &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="n"&gt;ALWAYS&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'english'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; 
  &lt;span class="n"&gt;STORED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will add a column called &lt;code&gt;fts&lt;/code&gt; to the books table. While inserting you have to ignore this column as this is a generated column.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The first argument to the &lt;code&gt;to_tsvector&lt;/code&gt; is the language we are using. The default gets picked up from the instance's locale settings. You can change this to work with other languages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that we have our data and the tsvector, let's do some search!&lt;/p&gt;

&lt;p&gt;For searching, we use the &lt;code&gt;@@&lt;/code&gt; operator in Postgres. &lt;code&gt;@@&lt;/code&gt; returns true if the search succeeds and false otherwise.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;fts&lt;/span&gt; &lt;span class="o"&gt;@@&lt;/span&gt; &lt;span class="n"&gt;plainto_tsquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chip'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was getting a few thousand results which were hard to show, so I put a limit of two here.&lt;/p&gt;

&lt;p&gt;The output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;           title
&lt;span class="nt"&gt;---------------------------&lt;/span&gt;
 Refined Metal Chips
 Intelligent Granite Chips
&lt;span class="o"&gt;(&lt;/span&gt;2 rows&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see our search works. We searched for the chip and it returned the first two results it found.&lt;/p&gt;

&lt;h2&gt;
  
  
  But how fast?
&lt;/h2&gt;

&lt;p&gt;Let's analyze the search, but this time with a new query so that we don't hit the cache and get raw results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYZE&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;fts&lt;/span&gt; &lt;span class="o"&gt;@@&lt;/span&gt; &lt;span class="n"&gt;plainto_tsquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'style'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;                                                          QUERY PLAN
&lt;span class="nt"&gt;---------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
 Gather  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1000.00..17860.20 &lt;span class="nv"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8297 &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;150&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;actual &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.578..95.603 &lt;span class="nv"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8329 &lt;span class="nv"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
   Workers Planned: 2
   Workers Launched: 2
   -&amp;gt;  Parallel Seq Scan on books  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.00..16030.50 &lt;span class="nv"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3457 &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;150&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;actual &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.461..86.162 &lt;span class="nv"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2776 &lt;span class="nv"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3&lt;span class="o"&gt;)&lt;/span&gt;
         Filter: &lt;span class="o"&gt;(&lt;/span&gt;fts @@ plainto_tsquery&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'style'&lt;/span&gt;::text&lt;span class="o"&gt;))&lt;/span&gt;
         Rows Removed by Filter: 30557
 Planning Time: 0.191 ms
 Execution Time: 96.159 ms
&lt;span class="o"&gt;(&lt;/span&gt;8 rows&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see we are getting about 8000 results in about 100ms! This is fast but we can do better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Index to the rescue!
&lt;/h2&gt;

&lt;p&gt;Postgres recommends using a &lt;code&gt;GIN&lt;/code&gt; index. Actually, You can use either &lt;code&gt;GIN&lt;/code&gt; or the &lt;code&gt;GiST&lt;/code&gt; index type. You can learn the differences between them &lt;a href="https://www.postgresql.org/docs/13/textsearch-indexes.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's create a GIN index on our &lt;code&gt;fts&lt;/code&gt; column:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;books_search_idx&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;GIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can run &lt;code&gt;\d books&lt;/code&gt; to see the created index in the psql client. The output will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;                                                                Table &lt;span class="s2"&gt;"public.books"&lt;/span&gt;
   Column    |   Type   | Collation | Nullable |                                               Default
&lt;span class="nt"&gt;-------------&lt;/span&gt;+----------+-----------+----------+-----------------------------------------------------------------------------------------------------
 &lt;span class="nb"&gt;id&lt;/span&gt;          | bigint   |           | not null | nextval&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'books_id_seq'&lt;/span&gt;::regclass&lt;span class="o"&gt;)&lt;/span&gt;
 title       | text     |           |          |
 description | text     |           |          |
 price       | numeric  |           |          |
 fts         | tsvector |           |          | generated always as &lt;span class="o"&gt;(&lt;/span&gt;to_tsvector&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'english'&lt;/span&gt;::regconfig, &lt;span class="o"&gt;(&lt;/span&gt;title &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;::text&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; description&lt;span class="o"&gt;))&lt;/span&gt; stored
Indexes:
    &lt;span class="s2"&gt;"books_pkey"&lt;/span&gt; PRIMARY KEY, btree &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="s2"&gt;"books_search_idx"&lt;/span&gt; gin &lt;span class="o"&gt;(&lt;/span&gt;fts&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's try our search once again to see how much faster it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;                                                           QUERY PLAN
&lt;span class="nt"&gt;--------------------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
 Bitmap Heap Scan on books  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;84.55..7554.73 &lt;span class="nv"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8297 &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;150&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;actual &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4.786..17.013 &lt;span class="nv"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8329 &lt;span class="nv"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
   Recheck Cond: &lt;span class="o"&gt;(&lt;/span&gt;fts @@ plainto_tsquery&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'style'&lt;/span&gt;::text&lt;span class="o"&gt;))&lt;/span&gt;
   Heap Blocks: &lt;span class="nv"&gt;exact&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4127
   -&amp;gt;  Bitmap Index Scan on books_search_idx  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.00..82.47 &lt;span class="nv"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8297 &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;actual &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.125..3.126 &lt;span class="nv"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8329 &lt;span class="nv"&gt;loops&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;
         Index Cond: &lt;span class="o"&gt;(&lt;/span&gt;fts @@ plainto_tsquery&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'style'&lt;/span&gt;::text&lt;span class="o"&gt;))&lt;/span&gt;
 Planning Time: 0.425 ms
 Execution Time: 18.217 ms
&lt;span class="o"&gt;(&lt;/span&gt;7 rows&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;8k results in ~20ms in a 100k row table. This is crazy fast!&lt;/p&gt;

&lt;h2&gt;
  
  
  The End
&lt;/h2&gt;

&lt;p&gt;There is a lot that you can do with text search in Postgres. It is impossible to cover all of them in a single blog 😉&lt;/p&gt;

&lt;h3&gt;
  
  
  Further readings
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/13/textsearch.html" rel="noopener noreferrer"&gt;Postgres docs on full text search&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/13/textsearch-indexes.html" rel="noopener noreferrer"&gt;Postgres text search index types&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>postgres</category>
      <category>sql</category>
      <category>database</category>
    </item>
    <item>
      <title>Postgres Triggers</title>
      <dc:creator>Siddhant Kumar</dc:creator>
      <pubDate>Fri, 09 Apr 2021 14:37:43 +0000</pubDate>
      <link>https://forem.com/siddhantk232/postgres-triggers-5h0b</link>
      <guid>https://forem.com/siddhantk232/postgres-triggers-5h0b</guid>
      <description>&lt;p&gt;Today in this blog, I want to talk about Postgres triggers. I will also go through an example use case to learn how to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scenario
&lt;/h2&gt;

&lt;p&gt;I have an e-commerce app to sell books and I am implementing the cart functionality in the database. So, there is a carts table that stores unique cart entries per user and a books table. For cart items, we have another table, &lt;code&gt;carts_books&lt;/code&gt;. The &lt;code&gt;carts&lt;/code&gt; and &lt;code&gt;carts_books&lt;/code&gt; are using a many-to-many relation. So, A cart can have many books, and a book can be used in many carts.&lt;/p&gt;

&lt;p&gt;Whenever a row is inserted or updated in the &lt;code&gt;carts_books&lt;/code&gt; table, I want to calculate the total price of the cart used. I also want to take the &lt;code&gt;multiplier&lt;/code&gt; column of the &lt;code&gt;carts_books&lt;/code&gt; table into account. This &lt;code&gt;multiplier&lt;/code&gt; column is the quantity of a single book. That means I am allowing a customer to buy many copies of a book.&lt;/p&gt;

&lt;p&gt;Let's use Postgres triggers to solve this problem. Before that, below is the list of columns of every table discussed so far.&lt;/p&gt;

&lt;h3&gt;
  
  
  The schema
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                               Table "public.carts"
   Column    |           Type           | Collation | Nullable |      Default
-------------+--------------------------+-----------+----------+--------------------
 created_at  | timestamp with time zone |           | not null | now()
 updated_at  | timestamp with time zone |           | not null | now()
 archived_at | timestamp with time zone |           |          |
 amount      | numeric                  |           | not null |
 status      | text                     |           | not null | 'ENQUEUED'::text
 user_id     | text                     |           | not null |
 id          | uuid                     |           | not null | gen_random_uuid()


                                     Table "public.books"
   Column    |           Type           | Collation | Nullable |              Default
-------------+--------------------------+-----------+----------+-----------------------------------
 created_at  | timestamp with time zone |           | not null | now()
 updated_at  | timestamp with time zone |           | not null | now()
 archived_at | timestamp with time zone |           |          |
 name        | text                     |           | not null |
 price       | numeric                  |           | not null |
 user_id     | bigint                   |           | not null |
 id          | bigint                   |           | not null | nextval('books_id_seq'::regclass)
 description | text                     |           | not null |

              Table "public.carts_books"
   Column   |  Type   | Collation | Nullable | Default
------------+---------+-----------+----------+---------
 book_id    | bigint  |           | not null |
 multiplier | integer |           | not null |
 user_id    | text    |           | not null |
 cart_id    | uuid    |           | not null |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;We want to run a procedure (also known as a function) after a row in the table&lt;code&gt;carts_books&lt;/code&gt; is INSERTED or UPDATED.&lt;/p&gt;

&lt;p&gt;For this let's create two Postgres triggers, one for UPDATE and the other for INSERT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;update_cart_price&lt;/span&gt;
  &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;carts_books&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
  &lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;PROCEDURE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_cart_price&lt;/span&gt; &lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;COMMENT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;update_cart_price&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;carts_books&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; 
&lt;span class="s1"&gt;'update the price of the related cart based on the updated cart item(s) in relation carts_books'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;calculate_cart_price&lt;/span&gt;
  &lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;carts_books&lt;/span&gt;
  &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
  &lt;span class="k"&gt;EXECUTE&lt;/span&gt; &lt;span class="k"&gt;PROCEDURE&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update_cart_price&lt;/span&gt; &lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;COMMENT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;calculate_cart_price&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;carts_books&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; 
&lt;span class="s1"&gt;'update the price of the related cart based on the updated cart item(s) in relation carts_books'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Above is the code that creates two Postgres triggers. Let's understand the syntax and what each clause used above means.&lt;/p&gt;

&lt;p&gt;To create a Postgres trigger, we want to tell Postgres about the following things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Name of the trigger. In this case, I have used names, &lt;code&gt;update_cart_price&lt;/code&gt; and &lt;br&gt;
&lt;code&gt;calculate_cart_price&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When you want the code to run? I want to know the values that are inserted, &lt;br&gt;
for this, I have to use the &lt;code&gt;AFTER INSERT&lt;/code&gt; and the &lt;code&gt;AFTER UPDATE&lt;/code&gt; clause.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How do you want to run this trigger? There are two options, one is to run this trigger for each affected row. Another option is to run the trigger `per statement.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What do you want to run? I have these triggers to run the procedure called &lt;code&gt;update_cart_price()&lt;/code&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;You must create the function before using it in a function.&lt;br&gt;
A function must return &lt;code&gt;TRIGGER&lt;/code&gt; in order to be used in a trigger.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The function that I want to run on these triggers is defined as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`sql&lt;/p&gt;

&lt;p&gt;CREATE OR REPLACE FUNCTION public.update_cart_price ()&lt;br&gt;
  RETURNS TRIGGER&lt;br&gt;
  AS $$&lt;br&gt;
DECLARE&lt;br&gt;
  item record;&lt;br&gt;
  new_amount numeric := 0;&lt;br&gt;
BEGIN&lt;br&gt;
  FOR item IN&lt;br&gt;
  SELECT&lt;br&gt;
    price,&lt;br&gt;
    multiplier&lt;br&gt;
  FROM&lt;br&gt;
    carts_books&lt;br&gt;
    JOIN books ON book_id = books.id&lt;br&gt;
  WHERE&lt;br&gt;
    cart_id = NEW.cart_id LOOP&lt;br&gt;
      new_amount := new_amount + (item.price * item.multiplier);&lt;br&gt;
    END LOOP;&lt;br&gt;
  UPDATE&lt;br&gt;
    carts&lt;br&gt;
  SET&lt;br&gt;
    amount = new_amount&lt;br&gt;
  WHERE&lt;br&gt;
    id = NEW.cart_id;&lt;br&gt;
  RETURN new;&lt;br&gt;
END;&lt;br&gt;
$$&lt;br&gt;
LANGUAGE plpgsql;&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This function selects the price and multiplier of all the books used inside the cart. It then updates the cart's amount column to reflect the updated amount.&lt;/p&gt;

&lt;p&gt;The data of the changed row is made available through the &lt;code&gt;new&lt;/code&gt; record.&lt;/p&gt;

&lt;p&gt;Also, the function cannot use plain &lt;code&gt;SQL&lt;/code&gt; language, you must use a procedural&lt;br&gt;
language.&lt;/p&gt;

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

&lt;p&gt;Postgres triggers make it easy to execute small business logic on data changes. &lt;/p&gt;

&lt;p&gt;Also, we do not need to care about the network latency that we would face if we execute the same logic in a different service (a nodejs app for example).&lt;/p&gt;

&lt;h3&gt;
  
  
  Good Reads
&lt;/h3&gt;

&lt;p&gt;In addition to this post, I also recommend reading these to know more about Postgres&lt;br&gt;
triggers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/13/trigger-definition.html"&gt;Official Docs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/13/trigger-datachanges.html"&gt;Data visibility in trigger functions&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>postgres</category>
      <category>sql</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Ultimate Hasura Development Setup</title>
      <dc:creator>Siddhant Kumar</dc:creator>
      <pubDate>Sun, 28 Feb 2021 14:36:28 +0000</pubDate>
      <link>https://forem.com/siddhantk232/the-ultimate-hasura-development-setup-3lgd</link>
      <guid>https://forem.com/siddhantk232/the-ultimate-hasura-development-setup-3lgd</guid>
      <description>&lt;p&gt;This post was originally published on my &lt;a href="https://siddhant.codes/posts/the-ultimate-hasura-development-setup/"&gt;website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post, we will look at how I set up and use Hasura graphql engine for local &lt;br&gt;
development with pgadmin4 and pldbgapi (pldebugger).&lt;/p&gt;
&lt;h2&gt;
  
  
  TLDR;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This setup uses &lt;a href="https://hasura.io/docs/latest/graphql/core/hasura-cli/index.html"&gt;Hasura cli&lt;/a&gt; to generate postgres migrations and hasura metadata.&lt;/li&gt;
&lt;li&gt;For debugging and other administrative operations, this setup uses &lt;a href="https://www.pgadmin.org/"&gt;pgAdmin4&lt;/a&gt; with &lt;a href="https://github.com/soycacan/pldebugger"&gt;pldbgapi&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;You can check this &lt;a href="https://github.com/siddhantk232/ultimate-hasura-dev-setup"&gt;sample project&lt;/a&gt; to get a better idea.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Hasura Cli setup
&lt;/h2&gt;

&lt;p&gt;To create a fresh hasura project, run &lt;code&gt;hasura init&lt;/code&gt;. This will create a folder with the name you provide. Inside this folder, it creates many subfolders for managing different components of your hasura server.&lt;/p&gt;

&lt;p&gt;The project structure looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── config.yaml                # main config file
├── metadata                   # hasura metadata
│   ├── actions.graphql
│   ├── actions.yaml
│   ├── allow_list.yaml
│   ├── cron_triggers.yaml
│   ├── functions.yaml
│   ├── query_collections.yaml
│   ├── remote_schemas.yaml
│   ├── tables.yaml
│   └── version.yaml
├── migrations                 # postgres migrations
└── seeds

3 directories, 10 files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The default config assumes you are running the graphql-engine at port 8080. You can change this setting in &lt;code&gt;config.yml&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Hasura local server with docker-compose
&lt;/h2&gt;

&lt;p&gt;To easily spin up many containers for postgres, hasura and pgadmin, I am using &lt;a href="//docs.docker.com/compose/"&gt;docker-compose&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;docker-compose.yml&lt;/code&gt; file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.6'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
      &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile.postgres&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:13&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db_volume:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;siddhant&lt;/span&gt;   &lt;span class="c1"&gt;# default postgres superuse&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;passwd&lt;/span&gt; &lt;span class="c1"&gt;# password for siddhant&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hasura_db&lt;/span&gt;
  &lt;span class="na"&gt;hasura&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hasura/graphql-engine:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080:8080"&lt;/span&gt; &lt;span class="c1"&gt;# default port expected by hasura cli&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db"&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;HASURA_GRAPHQL_DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres://siddhant:passwd@db:5432/hasura_db&lt;/span&gt;
      &lt;span class="c1"&gt;## enable the console served by server&lt;/span&gt;
      &lt;span class="na"&gt;HASURA_GRAPHQL_ENABLE_CONSOLE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt; &lt;span class="c1"&gt;# set to "false" to disable console&lt;/span&gt;
      &lt;span class="c1"&gt;## enable debugging mode. It is recommended to disable this in production&lt;/span&gt;
      &lt;span class="na"&gt;HASURA_GRAPHQL_DEV_MODE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
      &lt;span class="na"&gt;HASURA_GRAPHQL_ENABLED_LOG_TYPES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;startup, http-log, webhook-log, websocket-log, query-log&lt;/span&gt;
      &lt;span class="c1"&gt;## uncomment next line to set an admin secret&lt;/span&gt;
      &lt;span class="c1"&gt;# HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey&lt;/span&gt;
  &lt;span class="na"&gt;pgadmin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dpage/pgadmin4&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;PGADMIN_DEFAULT_EMAIL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test@test.com&lt;/span&gt;
      &lt;span class="na"&gt;PGADMIN_DEFAULT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;passwd&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:80"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pgadmin_volume:/var/lib/pgadmin&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db_volume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pgadmin_volume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Some important things in this docker-compose file:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Service &lt;code&gt;db&lt;/code&gt; uses postgres:13 official docker image with a custom Dockerfile to build this image.
This custom Dockerfile.postgres installs &lt;a href="https://github.com/soycacan/pldebugger"&gt;pldebugger&lt;/a&gt; extension for debugging procedural code.
The code for this Dockerfile.postgres looks like this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;    FROM postgres:latest

    ENV PG_MAJOR 13
    ENV PG_FULL 13.1

    &lt;span class="c"&gt;# Install the postgresql debugger&lt;/span&gt;
    RUN apt-get update \
    &amp;amp;&amp;amp; apt-get install -y --no-install-recommends \
    postgresql-$PG_MAJOR-pldebugger


    EXPOSE 5432
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To enable this extension you need to run &lt;code&gt;CREATE EXTENSION pldbgapi;&lt;/code&gt; in your postgres instance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Service &lt;code&gt;hasura&lt;/code&gt; uses the &lt;a href="https://hub.docker.com/r/hasura/graphql-engine"&gt;hasura-graphql-engine&lt;/a&gt; provided by Hasura.&lt;br&gt;
We are also exposing port 8080 so that this server is accessible by our hasura cli's console server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Serice &lt;code&gt;pgadmin&lt;/code&gt; use the &lt;a href="https://hub.docker.com/r/dpage/pgadmin4/"&gt;dpage/pgadmin4&lt;/a&gt; docker image.&lt;br&gt;
To use pgadmin we are exposing port 80 as 9090 on our system.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  That's it :)
&lt;/h2&gt;

&lt;p&gt;Now you can use this setup by running the command &lt;code&gt;docker-compose up -d&lt;/code&gt; inside the project folder. &lt;br&gt;
This command will start all three services for us to use.&lt;/p&gt;

&lt;p&gt;To access the hasura, console run &lt;code&gt;hasura console&lt;/code&gt; command inside the project file. &lt;br&gt;
This command will start a hasura console server that will record all your actions and update the &lt;code&gt;migrations&lt;/code&gt; and other folders accordingly.&lt;/p&gt;

&lt;p&gt;To access pgadmin, you can go to &lt;code&gt;localhost:9090&lt;/code&gt; on your browser. &lt;br&gt;
You can log in with the email and password you set in the docker-compose file. Also, for debugging your procedural inside pgadmin, read the docs here &lt;a href="https://www.pgadmin.org/docs/pgadmin4/latest/debugger.html"&gt;https://www.pgadmin.org/docs/pgadmin4/latest/debugger.html&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Run &lt;code&gt;CREATE EXTENSION pldbgapi;&lt;/code&gt; inside your postgres instance to enable the pldebugger extension.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Follow me on twitter: &lt;a href="https://twitter.com/siddhantk232"&gt;@siddhantk232&lt;/a&gt;&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>postgres</category>
      <category>sql</category>
      <category>docker</category>
    </item>
    <item>
      <title>Announcing my portfolio/blog website.</title>
      <dc:creator>Siddhant Kumar</dc:creator>
      <pubDate>Wed, 19 Aug 2020 12:12:39 +0000</pubDate>
      <link>https://forem.com/siddhantk232/announcing-my-portfolio-blog-website-5f96</link>
      <guid>https://forem.com/siddhantk232/announcing-my-portfolio-blog-website-5f96</guid>
      <description>&lt;p&gt;Hello everyone!&lt;/p&gt;

&lt;p&gt;I have recently built this blog/portfolio website using &lt;a href="https://www.11ty.dev/"&gt;11ty(eleventy)&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;You can check it out here on &lt;a href="https://siddhant.codes/"&gt;siddhant.codes&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;You can see the source code &lt;a href="https://github.com/siddhantk232/portfolio"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have not added any social media links right now but I will add them soon. For now, you can follow me &lt;a href="https://twitter.com/siddhantk232"&gt;here&lt;/a&gt; on twitter :)&lt;/p&gt;

&lt;h1&gt;
  
  
  Assets used.
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;I got the illustration on the homepage from &lt;a href="https://www.pixeltrue.com/free-illustrations"&gt;pixeltrue&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;Fonts used are Luckiest Guy and Montserrat. Available from google fonts&lt;/li&gt;
&lt;li&gt;For blogs, I have planned to use images from pexels or Unsplash. &lt;/li&gt;
&lt;li&gt;For code blocks, I used &lt;a href="https://prismjs.com/"&gt;prism.js&lt;/a&gt; with the monokai theme. I am planning to create a custom gruvbox prism css as I did not find it on the internet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Other than this I have also used some useful eleventy plugins for code blocks and image optimization which you can check out in the source code mentioned above.&lt;/p&gt;

&lt;h1&gt;
  
  
  Things I am planning to add.
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;comments section&lt;/strong&gt;. For this, I am thinking of using Github issues. So the plan is whatever you comment for a certain blog will be stored as a GitHub issue on my repo. I got this idea from &lt;a href="https://os.phil-opp.com/freestanding-rust-binary/"&gt;here&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;monthly newsletter&lt;/strong&gt;. I may use SendGrid for this as it is quite easy to use.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Deployment
&lt;/h1&gt;

&lt;p&gt;This project is hosted on netlify with a custom domain from name.com. Also, the SSL certificate is auto issued from netlify which is quite nice.&lt;/p&gt;

&lt;p&gt;I would love to know what you guys think. Please share how I can improve this :)&lt;/p&gt;

&lt;p&gt;Bye devs!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Using javascript custom events to make a notification service.</title>
      <dc:creator>Siddhant Kumar</dc:creator>
      <pubDate>Wed, 01 Jul 2020 10:42:48 +0000</pubDate>
      <link>https://forem.com/siddhantk232/using-javascript-custom-events-to-make-a-notification-service-4c6</link>
      <guid>https://forem.com/siddhantk232/using-javascript-custom-events-to-make-a-notification-service-4c6</guid>
      <description>&lt;p&gt;Read this blog on my website &lt;a href="https://siddhant.codes/posts/notification-service-in-webapps-using-custom-javascript-events/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Recently, I came across this &lt;a href="https://flaviocopes.com/javascript-custom-events/"&gt;article&lt;/a&gt; by &lt;strong&gt;Flavio copes&lt;/strong&gt; on "Custom events in javascript" and after reading that I got the idea for this post.&lt;/p&gt;

&lt;p&gt;So today we will implement a simple notification service built using vanilla js :)&lt;/p&gt;

&lt;p&gt;I am going to integrate this notification service in a todo app that I have created. You can find the finished code &lt;a href="https://github.com/siddhantk232/custom-js-event-example"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  let's take a look at how our code looks like.
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;index.html&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"style.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Document&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;main&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;form&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"todo"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"todos"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"todo"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;add todo&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"todos"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"notify"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"main.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I have a simple form with an &lt;code&gt;input:text&lt;/code&gt; and &lt;code&gt;button:submit&lt;/code&gt;. And I also have a &lt;code&gt;ul#todos&lt;/code&gt; for injecting in the todos. Other than that we also have a &lt;code&gt;div#notify&lt;/code&gt;, we will use this to position our notification container on the top-right of the screen.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;style.css&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Helvetica&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;73&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;129&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;233&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;list-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;157&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;185&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;236&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;83&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;116&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.add-display&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.notification-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;130&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;151&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;190&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;#notify&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&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;Pretty basic, right?&lt;/p&gt;

&lt;p&gt;Apart from the basic form and body styles, I have added &lt;code&gt;position: fixed;&lt;/code&gt; on the &lt;code&gt;#notify&lt;/code&gt; div. This will prevent the notification container from moving onScroll.&lt;br&gt;
Also, I have the &lt;code&gt;.notification-container&lt;/code&gt; which we are using in the javascript.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;main.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forms&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notificationHolder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#notify&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;notify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;notify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;preventDefault&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;todo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`

    &amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;todo&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;&amp;lt;/li&amp;gt;

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

  &lt;span class="nx"&gt;todo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;notificationHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&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="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;add-display&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;notify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;e&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;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;notification-container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Todo added !`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;notificationHolder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insertAdjacentElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;beforeend&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;container&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;Here, we are using listening on the &lt;code&gt;submit&lt;/code&gt; form event to add todos in our &lt;code&gt;ul#todos&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Just below that, we have one more event listener which is listening for the &lt;code&gt;notify&lt;/code&gt; event, that's is a custom event that we have created. Let's focus on the lines which are used to create this custom event.&lt;/p&gt;

&lt;p&gt;so on top 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;notify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Event"&gt;Event&lt;/a&gt; constructor to register a new event with the name 'notify' in our code. The name here is important because we will use this name only to listen for this custom event in our code.&lt;/p&gt;

&lt;p&gt;Now in the &lt;code&gt;submit&lt;/code&gt; listener callback function we are using &lt;code&gt;document.dispatchEvent(notify);&lt;/code&gt; to trigger this event. Also, we are setting a timeout function to clear all notification after 2.5 seconds (minimum).&lt;/p&gt;

&lt;p&gt;In the end, we are listening to our custom event &lt;code&gt;notify&lt;/code&gt; and inserting a new notification container inside our &lt;code&gt;div#notify&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/fGN8HFHfskA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;That's it. Our notification service is ready. I know you might have some questions, let's address them below:&lt;/p&gt;

&lt;h3&gt;
  
  
  Questions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  what about modularity?
&lt;/h4&gt;

&lt;p&gt;well, this was just a todo app and the implementation of custom events here is so small. If you have a big app that is using plain HTML, CSS, and js, then you can have a separate file that has custom Event declared and export functions for dispatching that event, and you can listen for that specific event in that file only. &lt;/p&gt;

&lt;p&gt;If your project uses libraries like react or vue and state machines like mobX or redux then you may not need to write custom Event yourself.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to pass custom notification message?
&lt;/h4&gt;

&lt;p&gt;Well, I do not have an answer for how to pass something when dispatching the event and use that passed value in the callback function of a custom event listener. So you know then please share.&lt;/p&gt;

&lt;p&gt;If I had to fire multiple notifications like &lt;code&gt;todo added&lt;/code&gt;, &lt;code&gt;logged in&lt;/code&gt;, and &lt;code&gt;logged out&lt;/code&gt; I would create a new &lt;code&gt;Event&lt;/code&gt; for each of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More questions&lt;/strong&gt; ask in comments&lt;/p&gt;

&lt;p&gt;If you are on twitter, consider following &lt;a href="//twitter.com/siddhantk232"&gt;me&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
