<?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: SaaSHub</title>
    <description>The latest articles on Forem by SaaSHub (@saashub).</description>
    <link>https://forem.com/saashub</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%2Forganization%2Fprofile_image%2F1050%2Feb80022f-3fbd-447e-8d6e-e89b5ac06423.png</url>
      <title>Forem: SaaSHub</title>
      <link>https://forem.com/saashub</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/saashub"/>
    <language>en</language>
    <item>
      <title>How to steal a website and how to prevent it</title>
      <dc:creator>Stan Bright</dc:creator>
      <pubDate>Tue, 13 Dec 2022 03:23:56 +0000</pubDate>
      <link>https://forem.com/saashub/how-to-steal-a-website-12p7</link>
      <guid>https://forem.com/saashub/how-to-steal-a-website-12p7</guid>
      <description>&lt;p&gt;OK, this is going to be a quick showcase of how someone proxy-mirrored my website in a way that is very difficult to detect or prevent it. Moreover, I will share some of the helpful advice that was received from the Hacker News and /r/sysadmin communities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Here it is the case
&lt;/h2&gt;

&lt;p&gt;I noticed yesterday that one of my websites, &lt;a href="https://www.saashub.com" rel="noopener noreferrer"&gt;SaaSHub&lt;/a&gt;, is not present on bing.com; however, its content is being served on a different and very suspicious domain name - &lt;a href="https://www.bing.com/search?q=site%3Asukuns.us.to" rel="noopener noreferrer"&gt;sukuns.us.to&lt;/a&gt;. And, of course, I started digging my nginx logs - grepping for that domain name. All I could find was that domain name appearing as the referrer when accessing some images. Then, suspecting what might be going on, I started accessing some unpopular pages (on their domain name) and monitoring the logs for them. All requests to my server were coming from different IPs located in the US but with my browser's User-Agent. At that point, I was pretty sure what was happening - someone was proxy-mirroring SaaSHub and serving it on-demand on their domain name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2fghvxxcy77px2u2tr4l.png" rel="noopener noreferrer"&gt;Here it is a simplified diagram of the case&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I started thinking, brainstorming and searching for some generic advice on what I could do to prevent this...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1 - block their IP&lt;/strong&gt;. Unfortunately, that's not very effective in general, as they could easily change their IP address. What is more, in this particular case, they are using a very wide range of IPs coming from a variety of different ASNs (some of the cases I caught - AS55081, 35913, 36352, 21769, 52393, 394814, 55286). What is more, at first glance, collecting those IPs and ASNs isn't a trivial/quick task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 2 - block them by user-agent&lt;/strong&gt; - Again, not an option as they are copying the user-agent of the user making the request on their domain-name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 3 - add a small piece of JavaScript that checks for the domain name, and if not &lt;a href="http://www.saashub.com" rel="noopener noreferrer"&gt;www.saashub.com&lt;/a&gt; - redirect to it&lt;/strong&gt;. This isn't a viable option in this case as the perpetrator is stripping all &lt;code&gt;&amp;lt;javascript&amp;gt;&lt;/code&gt; tags and files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 4 - use absolute URLs everywhere&lt;/strong&gt;. Well, they are rewriting all mentions of &lt;a href="http://www.saashub.com" rel="noopener noreferrer"&gt;www.saashub.com&lt;/a&gt; in links to their own domain name. So that doesn't seem like an option, too.&lt;/p&gt;

&lt;p&gt;At that point, I felt helpless and asked for help on &lt;a href="https://news.ycombinator.com/item?id=33952114" rel="noopener noreferrer"&gt;Hacker News&lt;/a&gt; &amp;amp; &lt;a href="https://old.reddit.com/r/sysadmin/comments/zjs60m/someone_is_proxymirroring_my_website_can_i_do/" rel="noopener noreferrer"&gt;/r/sysadmin&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Possible solutions and advice
&lt;/h2&gt;

&lt;p&gt;i.e. what can you do if this happens to you... based on the suggestions and advice that I received on HN &amp;amp; /r/sysadmin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1) File a DMCA to their host&lt;/strong&gt;. I've sent a request to their free domain name provider so far.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2) Covert the whole website to JavaScript&lt;/strong&gt;. Yup, that would work, but then no one without JS would be able to open SaaSHub, I'm guessing SEO will suffer too. So, this is not a path I'd like to take.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3) Block all images being accessed with a referrer that's not the expected domain name&lt;/strong&gt; - I haven't implemented this one yet, but this could actually work in a generic way. I'm keeping this one in my toolbox of viable options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4) Implement a honey-trap URL so that all their IPs could be exposed and blocked&lt;/strong&gt;. This could work as well. "All" you have to do is add an endpoint that is not accessible by normal users - e.g. /honeytrap?some-random-param=rand-number (Note: you need the randomness as the culprit is loading each page once only and caching it after that). Then, add a cron script that will call that end-point on the proxy-mirror domain name and expose all their IPs. That way, we can block them in a relatively automated manner (as long as we know their domain name).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5) Add some hidden and random text to your pages&lt;/strong&gt;. Why? So that you can search for that text later on and find out all the domain names that mirror your content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moral of the story
&lt;/h2&gt;

&lt;p&gt;We are not absolutely helpless! It is unfortunate that we have to deal with it, but, as they say - it is what it is. At least we have a small toolbox of viable options on how to react and what we can try out. &lt;/p&gt;

&lt;p&gt;Also, I guess there could be other things and means available to prevent and mitigate similar vicious actions. I'd be happy if you shared your experience and solutions in the comments here. You can find more advice within the discussions linked above, too. Nevertheless, whatever we (you and I) decide to do in similar cases, it's always a difficult and time-consuming battle. And I believe that bringing some awareness to this might help others resolve it quicker than I.&lt;/p&gt;

&lt;p&gt;As of now, I think that the report to their free DNS provider has worked out, as their domain-name is not responding to an actual IP anymore. I will have to work on #5 from above, though, so that I can know when they move to a new domain-name.&lt;/p&gt;

&lt;p&gt;p.s. if someone working at Microsoft or Bing is reading this, please could you help get &lt;a href="https://www.saashub.com" rel="noopener noreferrer"&gt;SaaSHub&lt;/a&gt; indexed instead of the fake mirror sukuns.us.to. Thanks 🙇!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>security</category>
      <category>sysadmin</category>
    </item>
    <item>
      <title>How to speed up your Ruby/Rails app by ~5% with zero efforts</title>
      <dc:creator>Stan Bright</dc:creator>
      <pubDate>Mon, 04 May 2020 06:07:33 +0000</pubDate>
      <link>https://forem.com/saashub/how-to-speed-up-your-ruby-rails-app-by-5-with-zero-efforts-56mc</link>
      <guid>https://forem.com/saashub/how-to-speed-up-your-ruby-rails-app-by-5-with-zero-efforts-56mc</guid>
      <description>&lt;p&gt;This is going to be a quick one. Yet I think it might be helpful to almost everyone using ruby. A few days ago, I learned about a gem by Shopify - &lt;a href="https://github.com/Shopify/symbol-fstring"&gt;symbol-fstring&lt;/a&gt;. Thank you &lt;a href="https://twitter.com/strzibnyj/status/1256056172609630208"&gt;@strzibnyj&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It does something very simple - very well: &lt;strong&gt;Access symbols internal strings without duplicating them&lt;/strong&gt;. Why is that important?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In Ruby many APIs tend to accept symbols, but regularly convert them to string internally. The typical example is ActiveSupport::HashWithIndifferentAccess, but there are plenty more.&lt;/p&gt;

&lt;p&gt;The problem with this is that Symbol#to_s creates a new string every time it is invoked, and since it often happens in hotspots, it causes a lot of work for the garbage collector, and cause many identical strings to be kept in memory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To activate the gem in an app, all you need is adding the gem to your Gemfile and requiring the "patch".&lt;/p&gt;

&lt;p&gt;e.g. &lt;code&gt;gem 'symbol-fstring', require: 'fstring/all'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's all. SaaSHub has been running with it for 3 days now, and there haven't been any unexpected issues. Based on my rough benchmarks, a typical page like "&lt;a href="https://www.saashub.com/basecamp-alternatives"&gt;basecamp alternatives&lt;/a&gt;" responds about 5% faster and allocates about 5% fewer objects. &lt;/p&gt;

&lt;p&gt;I know, 5% isn't that much, yet it's "FREE". Moreover, this gem is maintained by a billion-dollar company, Shopify, popular with its high-quality dev standards. So, I guess it is both safe and worth it giving it a go.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>Replacing React with Preact. It was easy and worth it.</title>
      <dc:creator>Stan Bright</dc:creator>
      <pubDate>Fri, 17 Jan 2020 10:47:35 +0000</pubDate>
      <link>https://forem.com/saashub/replacing-react-with-preact-it-was-easy-and-worth-it-1fb0</link>
      <guid>https://forem.com/saashub/replacing-react-with-preact-it-was-easy-and-worth-it-1fb0</guid>
      <description>&lt;p&gt;I had been considering &lt;a href="https://preactjs.com/"&gt;preact&lt;/a&gt; for quite some time. After all, the sell is easy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;100% compatible with the React ecosystem (kind of)&lt;/li&gt;
&lt;li&gt;Much smaller (js bundle size)&lt;/li&gt;
&lt;li&gt;Faster (performance)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;About two years ago, I went to a local meet-up in Sydney where the presenter was sharing how they were integrating Preact successfully in some parts of Qantas. That was interesting. And he was convincing. Yet, given that everyone is using React, I thought it was a daunting task and never put the time to research it further. Until recently.&lt;/p&gt;

&lt;p&gt;I was working on optimizing the page-load speed of SaaSHub, and one of the paths was decreasing the JS bundle size. I played a bit with &lt;strong&gt;webpack-bundle-analyzer&lt;/strong&gt; and &lt;strong&gt;source-map-explorer&lt;/strong&gt;, and it was apparent that 35% of all the libs were taken by React &amp;amp; react-select. Then I remembered &lt;strong&gt;preact&lt;/strong&gt;... and decided to review it again.&lt;/p&gt;

&lt;p&gt;It happened that that task was more scary than difficult. After going through the docs, the whole switch to preact consisted of adding it to &lt;strong&gt;packages.json&lt;/strong&gt;, adding the relevant aliasing to webpack's build configs:&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="nx"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preact/compat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom/test-utils&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preact/test-utils&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;preact/compat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Must be below test-utils&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="nx"&gt;environment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and importing 'preact/debug' somewhere in the app:&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;import 'preact/debug'&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;That was all. So simple. Everything worked without touching another line of code. Of course, the process could be more complicated for web apps with more sophisticated code.&lt;/p&gt;

&lt;p&gt;What are the benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.saashub.com"&gt;SaaSHub&lt;/a&gt;'s JS bundle file-size decreased with 20%: from 577k down to 460k&lt;/li&gt;
&lt;li&gt;Faster JS (although I haven't benchmarked that, neither have felt it being slow)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the end, if you are working on optimizing your JS, and you don't have super complicated setup, I'd highly recommend giving &lt;strong&gt;preact&lt;/strong&gt; a go. It might be easier than you think.&lt;/p&gt;

&lt;p&gt;p.s. the next step will be replacing &lt;strong&gt;react-select&lt;/strong&gt; with &lt;a href="https://github.com/downshift-js/downshift"&gt;downshift&lt;/a&gt;. I've already implemented one small component with it, and it is amazing. Unfortunately, that migration will require a lot more code changes.&lt;/p&gt;

</description>
      <category>react</category>
      <category>preact</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Postgres Trigram indexes VS Algolia</title>
      <dc:creator>Stan Bright</dc:creator>
      <pubDate>Mon, 19 Aug 2019 09:06:23 +0000</pubDate>
      <link>https://forem.com/saashub/postgres-trigram-indexes-vs-algolia-1oma</link>
      <guid>https://forem.com/saashub/postgres-trigram-indexes-vs-algolia-1oma</guid>
      <description>&lt;p&gt;I want to share my experience of using the native "trigram indexes" of Postgres to achieve a robust "typeahead/autocomplete/fuzzy search" functionality. I am using Algolia as an example, as I have noticed that a lot of people are using it precisely for that. Yet, I believe that you could do perfectly fine by using only Postgres in many situations.&lt;/p&gt;

&lt;p&gt;What are trigram indexes? They are a special type of index in Postgres that is available by default; however, they have to be explicitly enabled. Here it is an extract from the &lt;a href="https://www.postgresql.org/docs/9.6/pgtrgm.html"&gt;official docs&lt;/a&gt;: &lt;/p&gt;

&lt;p&gt;&lt;em&gt;"The pg_trgm module provides functions and operators for determining the similarity of alphanumeric text based on trigram matching, as well as index operator classes that support fast searching for similar strings.", "A trigram is a group of three consecutive characters taken from a string."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For example, the set of trigrams in the string "cat" is " c", " ca", "cat", and "at ". The set of trigrams in the string "foo|bar" is " f", " fo", "foo", "oo ", " b", " ba", "bar", and "ar ".&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now let's follow what's necessary to build our autocomplete engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  1) Enable the Postgres extension
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CREATE EXTENSION IF NOT EXISTS pg_trgm&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2) Index the columns you are going to query
&lt;/h2&gt;

&lt;p&gt;Once you have enabled &lt;code&gt;pg_trgm&lt;/code&gt; you have to index &lt;code&gt;varchar&lt;/code&gt; and &lt;code&gt;text&lt;/code&gt; columns to make use of it. e.g. &lt;code&gt;CREATE INDEX index_fuzzy_searches_on_name_trgm ON fuzzy_searches USING gin(name gin_trgm_ops)&lt;/code&gt;. Notice &lt;strong&gt;USING gin(name gin_trgm_ops)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now we have very fast &lt;code&gt;LIKE&lt;/code&gt; queries with leading and trailing wild cards (&lt;code&gt;%&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  3) Prepare the query
&lt;/h2&gt;

&lt;p&gt;The next step is splitting the user query by spaces and adding a separate &lt;strong&gt;WHERE&lt;/strong&gt; clause per name. For example, if a user searches for "sales cloud". We'd end up with a query like &lt;code&gt;SELECT * FROM mytable WHERE name LIKE '%sales%' AND name LIKE '%cloud%'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A simplified implementation in Ruby on Rails could look like (in this case &lt;code&gt;ServiceSearch&lt;/code&gt; would be MATERIALIZED VIEW that I will explain down the post):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;relation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ServiceSearch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popular_first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;relation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"content LIKE ?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"%&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="si"&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;end&lt;/span&gt;
&lt;span class="n"&gt;relation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4) Search for different types of records simultaneously
&lt;/h2&gt;

&lt;p&gt;One of the use cases of using a dedicated full-text search engine is querying all your records simultaneously. That could be achieved through a &lt;em&gt;MATERIALIZED VIEW&lt;/em&gt;. Moreover, you can easily give priority to some records. For example, showing Products before Categories. I am managing my views by the awesome &lt;a href="https://ruby.libhunt.com/scenic-alternatives"&gt;scenic gem&lt;/a&gt;, but you don't need to.&lt;/p&gt;

&lt;p&gt;Here it is the body of an exemplary materialized view:&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;id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;searchable_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Service'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;searchable_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;post_services_count&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;LOWER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'approved'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'closed'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;UNION&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;searchable_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Category'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;searchable_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&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="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;LOWER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;categorizations&lt;/span&gt; &lt;span class="n"&gt;cats&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;cats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'approved'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;.&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;categories&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By having a view like that one, we can query multiple records with something as simple as:&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;fuzzy_searches&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%test%'&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real-world examples
&lt;/h2&gt;

&lt;p&gt;One of the top competitors of &lt;a href="https://www.saashub.com"&gt;SaaSHub&lt;/a&gt; - &lt;a href="https://alternativeto.net"&gt;alternativeto.net&lt;/a&gt; is using Algolia for their search autocomplete. I'd suggest testing their search and SaaSHub's. You won't find many differences in the UX or speed. &lt;/p&gt;

&lt;p&gt;In the end, if you need a simple autocomplete functionality for your project, and you are using Postgres, my suggestion is to consider its &lt;strong&gt;trigram&lt;/strong&gt; indexes + materialized views. Reaching out to external services may not be necessary and could be an overkill. However, I'm sure that there will be many cases in which it will make sense to pay for a professional SaaS product.&lt;/p&gt;




&lt;p&gt;p.s. you can find more &lt;a href="https://www.saashub.com/algolia-alternatives"&gt;Algolia alternatives&lt;/a&gt; on SaaSHub 🙈.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>postgres</category>
      <category>ruby</category>
    </item>
  </channel>
</rss>
