<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bolaji Ajani</title>
    <description>The latest articles on DEV Community by Bolaji Ajani (@bjthecod3r).</description>
    <link>https://dev.to/bjthecod3r</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%2F875999%2F759798fa-286c-4067-8232-4b293041df8e.jpeg</url>
      <title>DEV Community: Bolaji Ajani</title>
      <link>https://dev.to/bjthecod3r</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bjthecod3r"/>
    <language>en</language>
    <item>
      <title>Understand how to use the decorator design pattern (with PHP and JS/TS examples).</title>
      <dc:creator>Bolaji Ajani</dc:creator>
      <pubDate>Wed, 05 Nov 2025 13:48:09 +0000</pubDate>
      <link>https://dev.to/bjthecod3r/understand-how-to-use-the-decorator-design-pattern-with-php-and-jsts-examples-1p6d</link>
      <guid>https://dev.to/bjthecod3r/understand-how-to-use-the-decorator-design-pattern-with-php-and-jsts-examples-1p6d</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/bjthecod3r" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F875999%2F759798fa-286c-4067-8232-4b293041df8e.jpeg" alt="bjthecod3r"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/bjthecod3r/mastering-the-decorator-pattern-adding-behaviour-without-breaking-code-2ajb" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Mastering the Decorator Pattern: Adding Behaviour Without Breaking Code&lt;/h2&gt;
      &lt;h3&gt;Bolaji Ajani ・ Oct 26&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#designpatterns&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#typescript&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#php&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>designpatterns</category>
      <category>typescript</category>
      <category>php</category>
    </item>
    <item>
      <title>Full-Text Search: Why Tools Like OpenSearch, Elasticsearch, and Meilisearch Matter</title>
      <dc:creator>Bolaji Ajani</dc:creator>
      <pubDate>Fri, 31 Oct 2025 10:48:30 +0000</pubDate>
      <link>https://dev.to/bjthecod3r/mastering-full-text-search-why-tools-like-opensearch-elasticsearch-and-meilisearch-matter-31ao</link>
      <guid>https://dev.to/bjthecod3r/mastering-full-text-search-why-tools-like-opensearch-elasticsearch-and-meilisearch-matter-31ao</guid>
      <description>&lt;p&gt;Search is everywhere, whether you’re looking for a product on Amazon, a tweet on X, or a log entry in your system. But not all searches are created equal. A simple SQL LIKE &lt;code&gt;'%term%'&lt;/code&gt; query just doesn’t cut it when your users expect lightning-fast, typo-tolerant, and contextually smart results.&lt;/p&gt;

&lt;p&gt;That's where full-text search engines like &lt;strong&gt;&lt;a href="https://opensearch.org/" rel="noopener noreferrer"&gt;OpenSearch&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://www.elastic.co/elasticsearch" rel="noopener noreferrer"&gt;Elasticsearch&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://www.meilisearch.com" rel="noopener noreferrer"&gt;Meilisearch&lt;/a&gt;&lt;/strong&gt; come in.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore why full-text search matters, how it works behind the scenes, and walk through a hands-on Meilisearch example you can run locally using Docker, Postman, or any other tool capable of making HTTP requests such as cURL.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Full-Text Search?
&lt;/h2&gt;

&lt;p&gt;Full-text search allows users to find relevant results based on textual content and not exact matches.&lt;/p&gt;

&lt;p&gt;Instead of scanning through every record in a database, search engines analyze and index text data to make it searchable in milliseconds. They understand context to an extent, handle typos, and rank results by relevance, giving users what they mean, not just what they type.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Full-Text Search Works
&lt;/h2&gt;

&lt;p&gt;Here’s a high-level view of what happens under the hood.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Documents&lt;/strong&gt; are ingested into the search engine.&lt;/li&gt;
&lt;li&gt;They’re &lt;strong&gt;tokenized&lt;/strong&gt;,that is split into searchable terms.&lt;/li&gt;
&lt;li&gt;The engine builds an &lt;strong&gt;inverted index&lt;/strong&gt;, mapping words to documents that contain them.&lt;/li&gt;
&lt;li&gt;When a user searches, their query is analyzed, scored, and ranked by relevance.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4omzz3o2d4poueqju2pv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4omzz3o2d4poueqju2pv.png" alt=" " width="800" height="125"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Process Overview&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Full-Text Search Engines Matter
&lt;/h2&gt;

&lt;p&gt;Search engines exist because users expect relevance and speed. The following are some of the reasons why they are essential:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speed at Scale&lt;/strong&gt; - Uses inverted indexes to handle millions of document quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relevance Scoring&lt;/strong&gt; - Ranks results by context, frequency, and meaning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fuzzy Matching&lt;/strong&gt; - Finds “interstllar” when you meant “Interstellar.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filtering &amp;amp; Sorting&lt;/strong&gt; - Combine search with advanced filters (price, tags, etc).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt; - From single-node lightweight setups to multi-cluster systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Choosing the Right Search Engine
&lt;/h2&gt;

&lt;p&gt;There is no one-size-fits all in full-text search, different engines shine in different contexts. Here are some of the most popular ones and why teams choose them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Engine&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Why It’s a Good Choice&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Elasticsearch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enterprise-scale search &amp;amp; analytics&lt;/td&gt;
&lt;td&gt;Proven, battle-tested, and highly extensible with a rich query DSL. Great for logs, metrics, and large datasets.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenSearch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Self-hosted analytics and observability&lt;/td&gt;
&lt;td&gt;AWS-backed open-source fork of Elasticsearch. Ideal for dashboards, metrics, and log management with built-in visualization tools.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Meilisearch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Developer-friendly instant search&lt;/td&gt;
&lt;td&gt;Lightweight, fast, and typo-tolerant by default. Excellent for product catalogs, SaaS apps, and small-to-medium projects.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Typesense&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Product and document search&lt;/td&gt;
&lt;td&gt;Easy to deploy with automatic typo tolerance and fine-tuned relevance. Perfect for modern web apps and e-commerce sites.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Apache Solr&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Legacy or enterprise systems&lt;/td&gt;
&lt;td&gt;Java-based, Lucene-powered, and integrates well with Hadoop and big data pipelines. Solid choice for on-premise deployments.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Algolia&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Managed search-as-a-service&lt;/td&gt;
&lt;td&gt;Fully hosted, fast, and feature-rich with analytics and instant results. Ideal for teams that prefer not to manage infrastructure.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MongoDB Atlas Search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Search within NoSQL documents&lt;/td&gt;
&lt;td&gt;Built on Lucene, enables search directly inside MongoDB Atlas. Great for apps already using MongoDB.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PostgreSQL Full-Text Search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Embedded search in relational apps&lt;/td&gt;
&lt;td&gt;Built-in text search (&lt;code&gt;to_tsvector&lt;/code&gt;, &lt;code&gt;to_tsquery&lt;/code&gt;) with stemming and ranking. Suitable for smaller-scale or internal systems.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  When Databases Can Still Handle Search
&lt;/h3&gt;

&lt;p&gt;Sometimes you don’t need a dedicated search engine, your database might already include built-in full-text search capabilities.&lt;br&gt;&lt;br&gt;
Here’s how popular databases stack up for smaller-scale or embedded search needs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Search Feature&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;to_tsvector&lt;/code&gt;, &lt;code&gt;to_tsquery&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Native full-text search with stemming, ranking, and weighting. Great for moderate datasets or apps needing SQL + search in one place.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MySQL / MariaDB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MATCH() AGAINST()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Simple full-text search in &lt;code&gt;MyISAM&lt;/code&gt; and &lt;code&gt;InnoDB&lt;/code&gt; tables. Works for blogs, products, or smaller data sets but lacks fuzzy matching and relevance tuning.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SQLite&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FTS5 Extension&lt;/td&gt;
&lt;td&gt;Lightweight full-text indexing for mobile or desktop apps. Perfect for embedded search without a separate service.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MongoDB Atlas Search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lucene-powered search layer&lt;/td&gt;
&lt;td&gt;Provides fuzzy matching, scoring, and highlighting directly within MongoDB Atlas. Useful for document-based applications.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Oracle Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Oracle Text&lt;/td&gt;
&lt;td&gt;Enterprise-grade search supporting structured and unstructured data, but complex to configure and manage.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Microsoft SQL Server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full-Text Search Service&lt;/td&gt;
&lt;td&gt;Offers keyword and phrase search within text columns, suitable for enterprise systems already using SQL Server.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My advice is use your database's built-in search when your dataset is small, self-contained, or doesn't need typo tolerance. Use a dedicated search engine once you need fast relevance scoring, fuzzy matching, and scaling beyond a few hundred thousand records.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example: Building Search with &lt;a href="https://www.meilisearch.com" rel="noopener noreferrer"&gt;Meilisearch&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Let's set up Meilisearch locally using Docker Compose. We should also be able to index some data, and explore search queries. I've made the assumption that you have docker locally and understand how to use docker.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 1: Create docker-compose.yml
&lt;/h3&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.8'&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;meilisearch&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;getmeili/meilisearch:v1.24&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;meilisearch&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MEILI_MASTER_KEY=a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w&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;7700:7700"&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;./data.ms:/meili_data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Run it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may skip &lt;strong&gt;step 2&lt;/strong&gt;, and just go to &lt;a href="https://dev.to/bjthecod3r/mastering-full-text-search-why-tools-like-opensearch-elasticsearch-and-meilisearch-matter-31ao#download-postman-collection"&gt;this section&lt;/a&gt; where I've included a postman collection file that you can download and import as a collection to run the sample tests included in this article.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Add Sample Data
&lt;/h3&gt;

&lt;p&gt;Create a movies.json file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Inception"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genre"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sci-Fi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2010&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Interstellar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genre"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sci-Fi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2014&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The Dark Knight"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genre"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2008&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Tenet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"genre"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Thriller"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make the following curl request to index the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST 'http://localhost:7700/indexes/movies/documents' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w' \
-H 'Content-Type: application/json' \
--data-binary @movies.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"taskUid"&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="nl"&gt;"indexUid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"movies"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"enqueued"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"documentAdditionOrUpdate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"enqueuedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2025-10-31T08:09:40.644831635Z"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order for us to be able to filter results in Meilisearch, we also have to update the index settings with the attribute we need to filter by.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X PUT 'http://localhost:7700/indexes/movies/settings/filterable-attributes' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w' \
-H 'Content-Type: application/json' \
--data-binary '["genre", "year"]'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"taskUid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"indexUid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"movies"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"enqueued"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"settingsUpdate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"enqueuedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2025-10-31T09:11:12.86560951Z"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also need to configure attributes that are sortable;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X PUT 'http://localhost:7700/indexes/movies/settings/sortable-attributes' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w' \
-H 'Content-Type: application/json' \
--data-binary '["year"]'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"taskUid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"indexUid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"movies"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"enqueued"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"settingsUpdate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"enqueuedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2025-10-31T09:19:18.009525346Z"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may verify all configured attributes by checking the index settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X GET 'http://localhost:7700/indexes/movies/settings' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"displayedAttributes"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"searchableAttributes"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"filterableAttributes"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"genre"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"sortableAttributes"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"rankingRules"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"words"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"typo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"proximity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"attribute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"sort"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"exactness"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="nl"&gt;"stopWords"&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt;&lt;span class="nl"&gt;"nonSeparatorTokens"&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt;&lt;span class="nl"&gt;"separatorTokens"&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt;&lt;span class="nl"&gt;"dictionary"&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt;&lt;span class="nl"&gt;"synonyms"&lt;/span&gt;&lt;span class="p"&gt;:{},&lt;/span&gt;&lt;span class="nl"&gt;"distinctAttribute"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"proximityPrecision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"byWord"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"typoTolerance"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"minWordSizeForTypos"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"oneTypo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"twoTypos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="nl"&gt;"disableOnWords"&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt;&lt;span class="nl"&gt;"disableOnAttributes"&lt;/span&gt;&lt;span class="p"&gt;:[],&lt;/span&gt;&lt;span class="nl"&gt;"disableOnNumbers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="nl"&gt;"faceting"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"maxValuesPerFacet"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"sortFacetValuesBy"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"alpha"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;&lt;span class="nl"&gt;"pagination"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"maxTotalHits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="nl"&gt;"embedders"&lt;/span&gt;&lt;span class="p"&gt;:{},&lt;/span&gt;&lt;span class="nl"&gt;"searchCutoffMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"localizedAttributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"facetSearch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"prefixSearch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"indexingTime"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Try Different Searches
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Simple Search
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w' \
-H 'Content-Type: application/json' \
--data '{ "q": "inception" }'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"hits"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Inception"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"genre"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Sci-Fi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2010&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"inception"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"processingTimeMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"offset"&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="nl"&gt;"estimatedTotalHits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"requestUid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"019a3994-1965-7833-94a8-03fd4553bfeb"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Fuzzy Search
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w' \
-H 'Content-Type: application/json' \
--data '{ "q": "interstllar" }'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"hits"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Interstellar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"genre"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Sci-Fi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2014&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"interstllar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"processingTimeMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"offset"&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="nl"&gt;"estimatedTotalHits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"requestUid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"019a3994-a647-7993-b8b2-d8bd4882d031"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Filter by year
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w' \
-H 'Content-Type: application/json' \
--data '{ "q": "Sci-Fi", "filter": "year &amp;gt; 2010" }'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"hits"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Interstellar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"genre"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Sci-Fi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2014&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Sci-Fi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"processingTimeMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"offset"&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="nl"&gt;"estimatedTotalHits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"requestUid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"019a398c-7ad4-7522-be38-db9cb7152704"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Sort by Year (Descending)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST 'http://localhost:7700/indexes/movies/search' \
-H 'Authorization: Bearer a4qaSFPIfDbrwviM5XsogCK77Oa-YCtpKP4i6cAxr7w' \
-H 'Content-Type: application/json' \
--data '{ "q": "sci-fi", "sort": ["year:desc"] }'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"hits"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Interstellar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"genre"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Sci-Fi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2014&lt;/span&gt;&lt;span class="p"&gt;},{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Inception"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"genre"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Sci-Fi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2010&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"sci-fi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"processingTimeMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"offset"&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="nl"&gt;"estimatedTotalHits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"requestUid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"019a3996-f4a3-7930-8109-041465231893"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Download Postman collection
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://raw.githubusercontent.com/BJTheCod3r/devto-meilisearch-demo/main/demo.postman_collection.json" rel="noopener noreferrer"&gt;collection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Access Repository on Github
&lt;/h3&gt;

&lt;p&gt;You can find the complete Meilisearch demo, including the Docker setup, sample data and Postman collection, on GitHub.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/BJTheCod3r/devto-meilisearch-demo" rel="noopener noreferrer"&gt;mastering full text search meilisearch demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Search quietly shapes how people experience your product. When it works well, it feels natural, fast, and almost invisible. Full-text search makes your app return results that are relevant to what users mean, not just what they type. Whether you use Meilisearch, OpenSearch, or PostgreSQL’s text search, choosing the right tool can turn a simple lookup into an experience that feels truly effortless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cover Image:&lt;/strong&gt; by Teslariu Mihai&lt;/p&gt;

</description>
      <category>meilisearch</category>
      <category>elasticsearch</category>
      <category>opensearch</category>
      <category>fulltextsearch</category>
    </item>
    <item>
      <title>Mastering the Decorator Pattern: Adding Behaviour Without Breaking Code</title>
      <dc:creator>Bolaji Ajani</dc:creator>
      <pubDate>Sun, 26 Oct 2025 07:34:00 +0000</pubDate>
      <link>https://dev.to/bjthecod3r/mastering-the-decorator-pattern-adding-behaviour-without-breaking-code-2ajb</link>
      <guid>https://dev.to/bjthecod3r/mastering-the-decorator-pattern-adding-behaviour-without-breaking-code-2ajb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Open for extension, closed for modification."&lt;/em&gt;&lt;br&gt;
This single line from the &lt;strong&gt;SOLID principles&lt;/strong&gt; perfectly captures the spirit of the &lt;strong&gt;Decorator Pattern&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you need to extend functionality without touching existing code, the Decorator Pattern is one of the most elegant solutions in software design.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Decorator Pattern?
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Decorator Pattern&lt;/strong&gt; is a structural pattern that lets you dynamically add new behavior to objects without changing their original structure.&lt;/p&gt;

&lt;p&gt;Instead of building large inheritance trees, you “wrap” an existing object with one or more decorator classes that each add something new.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Analogy
&lt;/h3&gt;

&lt;p&gt;Think of a cup of coffee:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You start with a base drink — say, espresso.&lt;/li&gt;
&lt;li&gt;You can then add milk, sugar, or cream.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each addition enhances the coffee’s flavor without changing the core espresso class. That’s the Decorator Pattern in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the Decorator Pattern Satisfy SOLID Principles?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;S&lt;/strong&gt; - Single Responsibility: Each decorator adds one specific concern (e.g., logging, caching) to the base object without altering its core behaviour. For example, in a coffee shop system, a &lt;code&gt;MilkDecorator&lt;/code&gt; adds milk-related behaviour without changing the base Coffee class's responsibility.&lt;br&gt;
&lt;strong&gt;O&lt;/strong&gt; - Open/Closed: Extend behaviour by wrapping and not rewriting the class. This is where the Decorator Pattern excels. It allows new functionality to be added (extension) by wrapping objects with decorators, without modifying the original class's code. For instance, you can add a &lt;code&gt;SugarDecorator&lt;/code&gt; to a &lt;code&gt;Coffee&lt;/code&gt; object without touching the &lt;code&gt;Coffee&lt;/code&gt; class itself.&lt;br&gt;
&lt;strong&gt;L&lt;/strong&gt; - Liskov Substitution: Decorators can replace the base class since they share the same interface. Decorators typically implement the same base class as the object they decorate, ensuring they can be used interchangeably. For example, a &lt;code&gt;DecoratedCoffee&lt;/code&gt; (with milk or sugar) can be used anywhere a Coffee is expected, as long as the decorator correctly implements the interface. However, care must be taken to ensure decorators don't introduce unexpected behaviour that may break contracts.&lt;br&gt;
&lt;strong&gt;D&lt;/strong&gt; - Dependency Inversion: Both base and decorators depend on an abstraction, not each other's implementation. Both the base component and decorators depend on the same abstract interface, allowing flexibility in how components are composed. For example a &lt;code&gt;Coffee&lt;/code&gt; implementation, whether it's a base &lt;code&gt;Espresso&lt;/code&gt; or a decorated &lt;code&gt;ExpressoWithMilk&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Basic Example (PHP)
&lt;/h2&gt;

&lt;p&gt;Here is a lightweight PHP example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FileLogger&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Logging: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoggerDecorator&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;Logger&lt;/span&gt; &lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Logger&lt;/span&gt; &lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$message&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;class&lt;/span&gt; &lt;span class="nc"&gt;TimestampLogger&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LoggerDecorator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"["&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d H:i:s'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"] "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$message&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;class&lt;/span&gt; &lt;span class="nc"&gt;EncryptLogger&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;LoggerDecorator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;base64_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$message&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="nv"&gt;$logger&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;EncryptLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TimestampLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileLogger&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
&lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"User logged in"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Logging: [2025-10-18 21:00:00] VXNlciBsb2dnZWQgaW4=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Class Diagram - Logger
&lt;/h3&gt;

&lt;p&gt;The diagram below visualizes the structure of the Decorator Pattern in our above example.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logger defines the core interface.&lt;/li&gt;
&lt;li&gt;FileLogger implements the interface directly.&lt;/li&gt;
&lt;li&gt;LoggerDecorator wraps any Logger instance.&lt;/li&gt;
&lt;li&gt;Concrete decorators (TimestampLogger, EncryptLogger) extend the decorator to add new behaviour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5xet63fryla0eqjwzuok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5xet63fryla0eqjwzuok.png" alt=" " width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-Life Use Case in TypeScript: An Exensible API Client
&lt;/h2&gt;

&lt;p&gt;Now, let's build something that can be used in production, an &lt;strong&gt;API Request Handler&lt;/strong&gt; that can dynamically gain logging, caching or retry capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Define an Interface that all API clients will implement.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ApiClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Create a Base HTTP Client
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HttpClient&lt;/span&gt; &lt;span class="kr"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ApiClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;any&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;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="s2"&gt;`Fetching from: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Create an Abstract Decorator that each decorator will extend
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApiClientDecorator&lt;/span&gt; &lt;span class="kr"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ApiClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApiClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Add Behaviour-Specific Decorators
&lt;/h3&gt;

&lt;h4&gt;
  
  
  LoggingDecorator
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoggingDecorator&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ApiClientDecorator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;any&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;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="s2"&gt;`[LOG] Starting request: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="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="s2"&gt;`[LOG] Completed request: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  CachingDecorator
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CachingDecorator&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ApiClientDecorator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="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="s2"&gt;`[CACHE HIT] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="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="s2"&gt;`[CACHE MISS] &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  RetryDecorator
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RetryDecorator&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ApiClientDecorator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApiClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;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;attempt&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;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`[RETRY] Attempt &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Compose the Decorators
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&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;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApiClient&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;HttpClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Add decorators as layers&lt;/span&gt;
  &lt;span class="nx"&gt;client&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;RetryDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CachingDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LoggingDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="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;data1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://jsonplaceholder.typicode.com/users/1&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;data2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://jsonplaceholder.typicode.com/users/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;// Cached&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;User:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="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;h4&gt;
  
  
  Output
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[LOG] Starting request: https://jsonplaceholder.typicode.com/users/1
[CACHE MISS] https://jsonplaceholder.typicode.com/users/1
[LOG] Completed request: https://jsonplaceholder.typicode.com/users/1
[CACHE HIT] https://jsonplaceholder.typicode.com/users/1
User: James Anderson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Class Diagram - API Client
&lt;/h3&gt;

&lt;p&gt;The diagram below shows the layering of decorators around the base &lt;code&gt;HttpClient&lt;/code&gt;. Each decorator(Logging, Caching, Retry) wraps another, forming a stack of added behaviour that enhances the base client dynamically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8cnto6p1u0jiljfdo35c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8cnto6p1u0jiljfdo35c.png" alt=" " width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages &amp;amp; Disadvantages
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Advantages&lt;/th&gt;
&lt;th&gt;Disadvantages&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Flexible runtime composition&lt;/td&gt;
&lt;td&gt;Can introduce complexity with many layers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Promotes reusability&lt;/td&gt;
&lt;td&gt;Harder to debug due to nested wrappers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adheres to SOLID&lt;/td&gt;
&lt;td&gt;Order of decorators affects behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avoids deep inheritance&lt;/td&gt;
&lt;td&gt;Requires more setup code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When to Use
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You need optional features (e.g. caching)&lt;/li&gt;
&lt;li&gt;You want to add behaviour without subclassing.&lt;/li&gt;
&lt;li&gt;You want to compose behaviours at runtime.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The Decorator Pattern is one of the most flexible tools in a developer's design arsenal. It elegantly combines composition over inheritance with SOLID principles, allowing you to add features on the fly without breaking your existing code.&lt;/p&gt;

&lt;p&gt;Whether you're writing PHP backends, TypeScript services or you're working with Java, you're probably using decorators already, maybe without realizing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cover Image:&lt;/strong&gt; "Cafe Adventure" by Andrew Tanglao&lt;/p&gt;

</description>
      <category>designpatterns</category>
      <category>typescript</category>
      <category>php</category>
    </item>
    <item>
      <title>Understanding Laravel Model Observer, and Testing Using the Underlying Model</title>
      <dc:creator>Bolaji Ajani</dc:creator>
      <pubDate>Wed, 27 Dec 2023 02:06:32 +0000</pubDate>
      <link>https://dev.to/bjthecod3r/understanding-laravel-observers-and-testing-using-the-underlying-models-3ncn</link>
      <guid>https://dev.to/bjthecod3r/understanding-laravel-observers-and-testing-using-the-underlying-models-3ncn</guid>
      <description>&lt;p&gt;In my journey with Laravel, one feature that consistently proves its value is the Model Observer. These observers are not just a neat organizational tool; they are a robust mechanism for reacting to various Eloquent model events – like creating, updating, or deleting models. Just recently, while working on a model with a conditional nullable column, Laravel Observers again demonstrated their efficiency. Let's delve into what Laravel Model Observers are and how you can leverage them effectively in your projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Laravel Model Observer?
&lt;/h2&gt;

&lt;p&gt;In Laravel, a Model Observer is a class where you can group listener methods for various Eloquent model events. This pattern, derived from the Observer design pattern, is particularly useful for keeping your model-related logic clean and manageable. It lets you handle model events such as &lt;code&gt;creating&lt;/code&gt;, &lt;code&gt;updating&lt;/code&gt;, &lt;code&gt;deleting&lt;/code&gt;, etc., in a dedicated class, ensuring that your model classes remain lean and focused on their primary responsibilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating and Registering Observers:
&lt;/h2&gt;

&lt;p&gt;Creating a Laravel Observer is straightforward. Use the artisan command to generate an observer class, ideally naming it after the model it observes. For instance, to create an observer for an Addon model, you would use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:observer AddonObserver --model=Addon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command scaffolds an observer class in the &lt;code&gt;app\Observers&lt;/code&gt; directory. Next, you need to register this observer with the model. This can be done in the boot method of the &lt;code&gt;App\Providers\EventServiceProvider&lt;/code&gt; or any custom service provider like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use App\Models\Addon;
use App\Observers\AddonObserver;

public function boot(): void
{
    Addon::observe(AddonObserver::class);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, you can list your observer within the &lt;code&gt;$observers&lt;/code&gt; property of the &lt;code&gt;EventServiceProvider&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protected $observers = [
    Addon::class =&amp;gt; [AddonObserver::class],
];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practical Example: Ensuring Data Integrity
&lt;/h2&gt;

&lt;p&gt;Consider an Addon model with varying types, where the &lt;code&gt;one-time&lt;/code&gt; type does not require a &lt;code&gt;usable_count&lt;/code&gt;. To ensure data integrity and prevent potential issues, we can implement a check in the observer's creating method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App\Observers;

use App\Models\Addon;
use Exception;

class AddonObserver
{
    public function creating(Addon $addon): void
    {
        if ($addon-&amp;gt;type !== Addon::TYPE_ONE_TIME &amp;amp;&amp;amp; is_null($addon-&amp;gt;usable_count)) {
            throw new Exception(Addon::ADDON_TYPE_REQUIRES_USABLE_COUNT);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code throws an exception if an &lt;code&gt;Addon&lt;/code&gt; of a certain type is created without the necessary &lt;code&gt;usable_count&lt;/code&gt;, thereby safeguarding the application from incorrect data entries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Observers with Pest PHP
&lt;/h2&gt;

&lt;p&gt;The robustness of Laravel applications often hinges on the effectiveness of our Observers. Testing these Observers thoroughly is crucial, and &lt;a href="https://pestphp.com/" rel="noopener noreferrer"&gt;Pest PHP&lt;/a&gt; provides an expressive and elegant framework for this purpose. Let's explore a comprehensive test case that not only tests the Observer but also validates the behavior of the underlying model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use App\Models\Addon;
use Exception;

test("ensuring addon observer integrity with model testing", function () {
    // Scenario: Creating an Addon without a required usable_count
    $this-&amp;gt;expectException(Exception::class);
    $this-&amp;gt;expectExceptionMessage(Addon::ADDON_TYPE_REQUIRES_USABLE_COUNT);

    Addon::factory()-&amp;gt;create([
        'type' =&amp;gt; Addon::TYPE_LIMITED, // This type requires usable_count
        'usable_count' =&amp;gt; null         // Omitting usable_count
    ]);

    // Scenario: Successfully creating an Addon without needing a usable_count
    $addon = Addon::factory()-&amp;gt;create([
        'type' =&amp;gt; Addon::TYPE_ONE_TIME, // This type does not require usable_count
    ]);

    expect($addon)-&amp;gt;toBeInstanceOf(Addon::class);
    expect($addon-&amp;gt;usable_count)-&amp;gt;toBeNull();
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test effectively checks the Observer's functionality by interacting directly with the Addon model, ensuring that the Observer enforces the necessary business rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion:
&lt;/h2&gt;

&lt;p&gt;Laravel Model Observers provide a structured way to handle model events and keep your models focused on their core responsibilities. Coupled with rigorous testing, Observers can significantly enhance the reliability and maintainability of your Laravel projects. You might want to visit the &lt;a href="https://laravel.com/docs/10.x/eloquent#observers" rel="noopener noreferrer"&gt;official Laravel documentation&lt;/a&gt; to read more about what you can do with observers.&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>observer</category>
      <category>testing</category>
    </item>
    <item>
      <title>Setting up Continuous Deployment for a vue app on a VPS using Github Actions</title>
      <dc:creator>Bolaji Ajani</dc:creator>
      <pubDate>Sun, 31 Jul 2022 23:01:00 +0000</pubDate>
      <link>https://dev.to/bjthecod3r/setting-up-continuous-deployment-for-a-vue-app-on-a-vps-using-github-actions-3ghb</link>
      <guid>https://dev.to/bjthecod3r/setting-up-continuous-deployment-for-a-vue-app-on-a-vps-using-github-actions-3ghb</guid>
      <description>&lt;p&gt;&lt;strong&gt;Continuous Deployment&lt;/strong&gt; is a part of the &lt;strong&gt;Continuous Delivery&lt;/strong&gt; process in &lt;strong&gt;CI/CD&lt;/strong&gt;. This process covers the part of deploying the builds to production (or other environments), although to get to this part we must have touched some processes in &lt;strong&gt;Continuous Integration&lt;/strong&gt;. However, this article will majorly be focusing on automatic deployment. It's important to note that there are other configurations and setup that should be done to have a decent CI/CD process. For example, one might setup ways to manage and keep each release on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need to get started
&lt;/h2&gt;

&lt;p&gt;The assumption here is you have a github account and you have setup your github repository. It's also assumed that you have a Virtual Private Server that has been provisioned, if you do not have this, you can get it from any of the hosting platform (e.g. DigitalOcean, Linode) that offers this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;We need to set up the server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm currently using Ubuntu 20.04.2 LTS for this. It doesn't really matter what OS you use but if your setup has its peculiarities, just try to factor that in your modifications. If you don't already have ssh key pairs generated, do that by running the command below in your terminal.&lt;/p&gt;

&lt;p&gt;You can generate the keys on your local machine or on the remote server. If you'll be generating on the remote server run the next line (1.1) of code. If not skip and move to line 1.2.&lt;/p&gt;

&lt;p&gt;1.1. &lt;code&gt;ssh username@host&lt;/code&gt; where username is your username and host is probably the ip to your host.&lt;/p&gt;

&lt;p&gt;Then run;&lt;/p&gt;

&lt;p&gt;1.2. &lt;code&gt;ssh-keygen -t rsa -b 4096 -C "email@example.com"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You do not need to specify the flags, by default the best options available will be picked. &lt;code&gt;-t&lt;/code&gt; flag represents the algorithm type to use , &lt;code&gt;-b&lt;/code&gt; flag represents the size. You could also add an &lt;code&gt;-f&lt;/code&gt; flag which takes the &lt;em&gt;filename&lt;/em&gt; you want to store the key in. The &lt;code&gt;-C&lt;/code&gt; flag is for commenting the keyfile, you can put anything in the comment but it's common to put one's email in.&lt;/p&gt;

&lt;p&gt;After running the last command you'll be prompted to enter the file you want to store the keys in, just press enter. You'll be asked to specify a passphrase afterwards, press enter two times to set it up with no passphrase. However, if you specify a passphrase, you'll have to include it in your deploy script.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add the public key to your authorized keys&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run the command below to append the previously created public key to your authorized_keys. Although you can as well do that manually by copying the public key and opening your authorized_keys file to paste it at the end.&lt;/p&gt;

&lt;p&gt;2.1. &lt;code&gt;cat ~/.ssh/id_rsa.pub &amp;gt;&amp;gt; ~/.ssh/authorized_keys&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you created the keys on your local machine, run the following to copy the key to your remote server or you can as well just do it manually;&lt;/p&gt;

&lt;p&gt;2.2. &lt;code&gt;ssh-copy-id -i ~/.ssh/id_rsa.pub username@host&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copy the private key from your server or local machine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run the following command to do that&lt;/p&gt;

&lt;p&gt;3.1. &lt;code&gt;cat ~/.ssh/id_rsa&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The command will print out the content of the &lt;code&gt;id_rsa&lt;/code&gt; file to the terminal. Copy this to somewhere else you can easily access (like your clipboard) later. It should be noted that the name doesn't have to be &lt;code&gt;id_rsa&lt;/code&gt;, this depends on what you saved your keys as in the previous step or the kind of algorithm you're making use of. The private key should start with something like &lt;code&gt;-----BEGIN OPENSSH PRIVATE KEY-----&lt;/code&gt; and end with &lt;code&gt;-----END OPENSSH PRIVATE KEY-----&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setup Github Actions Secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go to the the app's github repository, then from there navigate to settings. Then use &lt;strong&gt;"New Repository Secret"&lt;/strong&gt; button repeatedly adding the following parameters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62c22uzhh06g3yqke004.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62c22uzhh06g3yqke004.png" alt=" " width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;SSHKEY - Input the key you copied in the previous step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HOST - Input your server's hostname (i.e ip address).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;USERNAME - Input the username you use to ssh into your sserver.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PORT - Input the ssh port no, default is 22.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TARGET - Input your vue app's root path .i.e &lt;code&gt;/var/www/staging&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;STAGING_ENV - This is optional, and you can name it whatever you want. I use the format ENVIRONMENT_ENV to specify the environment I'm deploying to. If you don't make use of the env file to store environment variables, this should be ignored.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PASSPHRASE - This is optional, if you left the passphrase empty in step 1 above, ignore this.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjx7beldad0ouwp03fv40.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjx7beldad0ouwp03fv40.png" alt=" " width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add YAML file to configure your repository for automatic deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are couple of ways to go about this. You can do this by going to &lt;strong&gt;"actions"&lt;/strong&gt; tab in your repository and then clicking on the link that says &lt;strong&gt;"set up a workflow yourself"&lt;/strong&gt;. Then copy the content of the YAML file that is below into the provided text box. You may change the name of the file to &lt;em&gt;whatevername.yml&lt;/em&gt; (in my case, I named it deploy-staging.yml because it's for the staging environment) and then commit it.&lt;/p&gt;

&lt;p&gt;However, for easy control, in your local repository, in the root folder create the following &lt;code&gt;.github/workflows/deploy-staging.yml&lt;/code&gt;where &lt;strong&gt;.github&lt;/strong&gt; and &lt;strong&gt;workflows&lt;/strong&gt; are directories. After doing this, you can then commit and push the changes to your remote repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy to Staging

on: 
  push:
    branches:
      - development
jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3

    - name: Copy repository contents to remote server via scp
      uses: appleboy/scp-action@master
      with:
        host: ${{ secrets.HOST }}
        username: ${{ secrets.USERNAME }}
        port: ${{ secrets.PORT }}
        key: ${{ secrets.SSHKEY }}
        passphrase: ${{ secrets.PASSPHRASE }}
        source: "."
        target: ${{ secrets.TARGET }}

    - name: Executing remote command via ssh
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.HOST }}
        username: ${{ secrets.USERNAME }}
        port: ${{ secrets.PORT }}
        passphrase: ${{ secrets.PASSPHRASE }}
        key: ${{ secrets.SSHKEY }}
        script: |
              cd ${{secrets.TARGET}} &amp;amp;&amp;amp; npm install
              printf "%s" "${{secrets.STAGING_ENV}}" &amp;gt; "${{secrets.TARGET}}/.env.staging"
              npm run build:staging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A run through of what the script is doing;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;In order not to deploy on every push, we have specified the branch(es) that pushing to must trigger the deploy process. Here, we specified the development branch, however we can specify multiple branches.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;checkout&lt;/strong&gt; action checks out the repository so our workflow can access it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;scp-action&lt;/strong&gt; handles the copying of the files from the repository to the remote server using the credentials we have provided via actions secrets. If you left the passphrase empty in step 1 above, remove the passphrase line.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ssh-action&lt;/strong&gt; handles running the remote command to facilitate the deployment, also using the credentials we provided via actions secret to access the remote server. If you left the passphrase empty in step 1 above, remove the passphrase line.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The commands are quite straightforward&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;navigate to the root directory then run &lt;code&gt;npm install&lt;/code&gt; to install the dependencies.&lt;/li&gt;
&lt;li&gt;write the environment variables stored in the STAGING_ENV secret into &lt;code&gt;.env.staging&lt;/code&gt; file on the remote server [This is an optional line, if it's not applicable to you, remove it].&lt;/li&gt;
&lt;li&gt;build the app using a custom script. I added a custom script, &lt;code&gt;"build:staging": "vue-cli-service build --mode staging"&lt;/code&gt;, in my package.json to build for the staging environment. If you do not have a special need like me, you might as well change that to &lt;code&gt;npm run build&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After pushing the changes to the specified branch(es). switch to the &lt;strong&gt;Actions&lt;/strong&gt; tab on your repository and you should get something like the below image if all went well. Now, you have successfully deployed your app to your environment of choice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6twzia420f3mt9oq7bdj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6twzia420f3mt9oq7bdj.png" alt=" " width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the above, we have used the set of actions listed below. You may check them out to learn one or more things you can equally do with them;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/marketplace/actions/checkout" rel="noopener noreferrer"&gt;Checkout&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/marketplace/actions/scp-files" rel="noopener noreferrer"&gt;SCP Files&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/marketplace/actions/ssh-remote-commands" rel="noopener noreferrer"&gt;SSH Remote Commands&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;NB:&lt;/strong&gt; &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There is a lot you can do with github actions and I would advise we explore this more. Visit the github actions &lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;page&lt;/a&gt; to learn what we can do and how you can better equip yourself.&lt;/li&gt;
&lt;li&gt;This process will also work for other related codebases, albeit with one or more modifications.&lt;/li&gt;
&lt;li&gt;I assumed you must have point your server block to the &lt;strong&gt;dist&lt;/strong&gt; directory, this directory is where vue builds the app by default.&lt;/li&gt;
&lt;li&gt;Github Actions has limited free minutes and storage for private repositories. You might want to check &lt;a href="https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions" rel="noopener noreferrer"&gt;here&lt;/a&gt; to see what is applicable to you.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I hope you find this useful. If you have questions or comments, please drop them below and let's learn together.&lt;/p&gt;

</description>
      <category>vue</category>
      <category>devops</category>
      <category>linux</category>
      <category>githubactions</category>
    </item>
  </channel>
</rss>
