<?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: saint-james-fr</title>
    <description>The latest articles on DEV Community by saint-james-fr (@saintjamesfr).</description>
    <link>https://dev.to/saintjamesfr</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%2F909189%2Ff60a0528-1ba3-453d-8eb0-9b221950704e.png</url>
      <title>DEV Community: saint-james-fr</title>
      <link>https://dev.to/saintjamesfr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saintjamesfr"/>
    <language>en</language>
    <item>
      <title>Build a No-Code Tech Newsletter Intelligence Agent with n8n and Vector Database</title>
      <dc:creator>saint-james-fr</dc:creator>
      <pubDate>Sun, 04 May 2025 21:26:09 +0000</pubDate>
      <link>https://dev.to/saintjamesfr/build-a-no-code-tech-newsletter-intelligence-agent-with-n8n-and-vector-database-19oi</link>
      <guid>https://dev.to/saintjamesfr/build-a-no-code-tech-newsletter-intelligence-agent-with-n8n-and-vector-database-19oi</guid>
      <description>&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%2F918zlqlnwafbrgj5ys7w.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%2F918zlqlnwafbrgj5ys7w.png" alt="Image description" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Fighting mental fatigue...&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Do you ever feel overwhelmed by tech newsletters? As a fullstack developer, you need to stay up to date across multiple areas — frontend, backend, databases, infrastructure, and more.&lt;/p&gt;

&lt;p&gt;Personally, I’m subscribed to at least 15 newsletters, and I love them all — spoiler alert: I list most of them at the end of the article. But if I miss a day or two, my “DEV” folder starts overflowing. I often spend hours on the weekend catching up — reading inspiring articles, testing snippets, and discovering new tools. It’s one of the best ways to grow and stay sharp.&lt;/p&gt;

&lt;p&gt;But let’s be honest — keeping up with everything is exhausting.&lt;/p&gt;

&lt;p&gt;So last weekend, I gave myself a challenge: build a system to manage the overflow using as little code as possible, combining AI and automation. I’m no expert— I had only heard of embeddings  and we use some at work— but I pulled it off. And you can too. 🙂 &lt;/p&gt;

&lt;h2&gt;
  
  
  What Are We Building?
&lt;/h2&gt;

&lt;p&gt;We’ll build a system that pulls content from your favorite tech newsletters, stores it in a vector database, and lets you query them conversationally — all in (almost) no-code using &lt;strong&gt;n8n&lt;/strong&gt;, &lt;strong&gt;Pinecone&lt;/strong&gt;, a bit of &lt;strong&gt;JavaScript&lt;/strong&gt;, and of course &lt;strong&gt;OpenAI&lt;/strong&gt;  - but you can use an other AI provider of course.&lt;/p&gt;

&lt;p&gt;We’ll need three workflows but one of them is optional:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gmail Trigger&lt;/strong&gt;: It is going to poll our gmail account and if there are new emails, it will get their full details, including html, create the embeddings and store them in the vectorized database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chat Interface&lt;/strong&gt;: This will leverage n8n’s chat interface to prompt ChatGPT with results from the vector database in an agentic way.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;(Optional) Webhook for Historical Ingestion&lt;/strong&gt;: This workflow processes older newsletters in bulk and required some code. I'll treat it last.&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%2Frk5yifjuw8t1hzd0fuxl.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%2Frk5yifjuw8t1hzd0fuxl.png" alt="Image description" width="800" height="1622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Services we’ll use:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Gmail&lt;/li&gt;
&lt;li&gt;OpenAI API&lt;/li&gt;
&lt;li&gt;Pinecone (free tier)&lt;/li&gt;
&lt;li&gt;n8n (free trial)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll assume that, like me, you’re using Gmail, and that you can set up forwarding rules.Also, you’ll need Node.js installed to run a small script if you go for the optional step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Oh and just before we go, let's just get a quick reminder about a concept we gonna use: what is an&lt;/strong&gt; &lt;strong&gt;&lt;em&gt;embedding&lt;/em&gt;&lt;/strong&gt; &lt;strong&gt;?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An embedding converts text (or images, audio, etc.) into a vector — a list of numbers — that represents its &lt;strong&gt;meaning&lt;/strong&gt; in a way a machine can understand. We store it in a vectorized database.&lt;br&gt;
Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text: &lt;code&gt;"How to build an agent?"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Embedding: &lt;code&gt;[0.12, -0.98, 1.45, ...]&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Let's synchronize emails
&lt;/h2&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%2F3lwbbp8ouz0c0cg8rq4h.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%2F3lwbbp8ouz0c0cg8rq4h.png" alt="Image description" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Create a Gmail account&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think it's easier not to mix everything as it only takes 3min, I recommend you create a new email adress for this exercise.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Forward your newsletters&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I already had a rule that filtered all tech newsletters into a “DEV” folder. I just added a forward rule to send them to the new address.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Connect Gmail to n8n&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ok let's dive in, you need a free trial n8n subscription and create a new workflow from scratch. You select the "on app event" trigger, choose Gmail and "Message received". From there you can create a credential using Oauth and connect to your newly created account. For testing, you can send yourself an email.&lt;/p&gt;

&lt;p&gt;One of the feature I liked the most on n8n, just like my previous experience with Integromat, is the possiblity to listen for an input (for example a webhook or an event) and analyze the input received. Immensely useful, given the fact that you can pin it for the next step also.&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%2F9n5ll6p4xfaqkqutn3pe.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%2F9n5ll6p4xfaqkqutn3pe.png" alt="Image description" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Get the full email content&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trigger doesn’t give you the full HTML body. You need to add a &lt;code&gt;Get Message&lt;/code&gt; node (still using the Gmail integration) and uncheck “Simplify” to get the raw HTML.&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%2Fmu2n807fsvkaxduynkv9.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%2Fmu2n807fsvkaxduynkv9.png" alt="Image description" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Handling multiple emails?&lt;/strong&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I remember having to create loop and handle array in my previous no-code experience with Make Integromat. But with n8n it's much more simpler. Thanks to their "multiple items" processing model, n8n automatically handles arrays of items by processing each one through the entire workflow. It's like every node is accepting an array - even if there is only a single element inside. This is brillant!&lt;/p&gt;

&lt;h2&gt;
  
  
  Convert to Embeddings &amp;amp; Store in Pinecone
&lt;/h2&gt;

&lt;p&gt;Now, we need to convert our html piece of code into a more LLM-friendly data format and this is why we need to convert it to an embedding. Embeddings come in different formats, depending on the model we use.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Create a Pinecone index&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll use the OpenAI &lt;code&gt;text-embedding-3-small&lt;/code&gt; model (1536 dimensions), which is more than enough for newsletter HTML.&lt;/p&gt;

&lt;p&gt;Thanks to &lt;strong&gt;Pinecone serverless architecture&lt;/strong&gt;, creating the index is very straightforward, I didn't touch to any of settings. The only thing we're going to need is its id, which we set up in the first input fields.&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%2F2tm3dmrmrge97h478rtw.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%2F2tm3dmrmrge97h478rtw.png" alt="Image description" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Set up the Pinecone node with embeddings&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create the node, enter your index ID and link it to the OpenAI embeddings node. This is very easily done with n8n, when a module needs another module, it just appears below, you click it and can navigate in a sub-menu to set everything up. Here you just have to click first on Embedding, choose the same model and enter your OpenApi API key.&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%2Fuwbooig5j1dxw7mozsbi.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%2Fuwbooig5j1dxw7mozsbi.png" alt="Image description" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Set up the Pinecone node with document&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We set up a Default data loader to access the &lt;code&gt;html&lt;/code&gt; field but we need to set what is called a separator. We choosed a basic setting as it was mentioned it will cover most use case: the recursive character text splitter. Why? In n8n, you're loading html content and need to break it into smart chunks for further processing. The separator defines how you split it. I'm sure there is era for improvement here as I choose the default and set options for split code to &lt;code&gt;html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vwj70vxfjd6s4eh7b0w.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%2F0vwj70vxfjd6s4eh7b0w.png" alt="Image description" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffruy8c0d8bs5w6sjxsi8.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%2Ffruy8c0d8bs5w6sjxsi8.png" alt="Image description" width="500" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You've just created your first AI pipeline!&lt;/p&gt;

&lt;h2&gt;
  
  
  Query the Database via Chat
&lt;/h2&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%2F9szz6kk0olh2z1w4ze2q.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%2F9szz6kk0olh2z1w4ze2q.png" alt="Image description" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here I was a bit lost but thanks to &lt;a href="https://n8n.io/workflows/" rel="noopener noreferrer"&gt;n8n great automation examples&lt;/a&gt; I just had a look at how someone analyzed a PDF and then chat with the result of it. That was close enough to our use-case. Let's create another workflow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use n8n's Chat Interface&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically, instead of relying of an app's event, here we choose to have a chat integration. n8n really made a difference here. You can keep your chat private, in the workflow, but also make it public, and even put it behind a login step (username/password or just your n8n credentials). Out-of-the-box!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Create an AI Agent in n8n&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI agent will use our tool when needed and attach it to ChatGPT when building prompts. This is simply done again by filling up the modules. That's why our agent needs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A connection to ChatGPT (the model)&lt;/li&gt;
&lt;li&gt;Access to the Pinecone vector DB (the tool)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;n8n connects these easily via built-in modules. Just fill the needed informations and credentials.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Craft a good system prompt&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A well-written system message boosts the answer quality. Here’s mine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  You are an expert assistant specialized in technologies and web development.
  You have access to a curated database of newsletters from top-tier sources, each provided with full HTML source code.
  When answering, you must:
  - Extract insights directly from the HTML content
  - Identify the original sender by examining the HTML metadata
  - Clearly cite the newsletter source and reception date
  - Always include the original link when available

  Your responses should be accurate, concise, and well-structured.
  Prioritize verifiable information from the newsletter content itself.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Backfill Old Emails (optional)
&lt;/h2&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%2F8r8z2exp3lzm6yae4enp.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%2F8r8z2exp3lzm6yae4enp.png" alt="Image description" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I wanted to ingest my backlog of newsletters too. The idea was simple : get my data from Google, transform it into JSON, send it via HTTP to a n8n worklow to store it in database. Let's do it!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Export from Gmail via Google Takeout&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Actually, it's quite simple to get your data from Google. There is a service for this called Google Takeout. I choosed the Gmail service, my DEV folder and... kabooom! A few moments later I got a zip with my data.&lt;/p&gt;

&lt;p&gt;I tried to open it and found inside a html page with a mbox data folder... I was not familiar with this format but it's used by many email clients to exchange data. But my n8n workflow don't speak MBOX.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Convert MBOX to JSON&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A little research led to me to this &lt;a href="https://github.com/brysonian/mbox-to-json" rel="noopener noreferrer"&gt;repository&lt;/a&gt;. Just clone the code and run the command mentioned in the README to get your emails as Json. Easy peasy!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="c"&gt;# Instal dependencies&lt;/span&gt;
  npm i
  &lt;span class="c"&gt;# Run the script (no need to use node, script uses a shebang)&lt;/span&gt;
  ./mbox-to-json.js &lt;span class="nt"&gt;--input&lt;/span&gt; my_emails.mbox &lt;span class="nt"&gt;--output&lt;/span&gt; my_emails.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok we finally have our JSON. What's the idea? Maybe we can build a little JS script to parse it, and send it via a POST request to another n8n workflow with a webhook that can receive data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Send JSON to n8n via Webhook&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically, I just copied our Gmail trigger workflow and changed the initial step to a webhook. You can chose an authentification method, just buid a token using openssl and put in your header of your choice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="c"&gt;# It's easy to build a simple token using openssl&lt;/span&gt;
  openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Analyzing answer&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Humm... Something is wrong. the body of our request contains an array of data, we need to map them before passing them to our Pinecode node - the same we used before. And here I must say I fell in love for real with n8n : you can write javascript code very easily inside the node. With the help of my  good friend Claude, I got the syntax right (it's not that easy at first to understand why you need &lt;code&gt;$input.first()&lt;/code&gt;) and then it's just basic JS.&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%2Fsblmut11dnw8y6p8ao11.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%2Fsblmut11dnw8y6p8ao11.png" alt="Image description" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Here comes the trouble...&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But here I encoutered many difficulties that I'll resume here&lt;/p&gt;

&lt;p&gt;First, my data was quite big, around 200Mo of JSON, this was way too much of a payload. I need to batch it.&lt;/p&gt;

&lt;p&gt;Then, even after batching it in slice of 10 elements, I've realized that the limits of my free instance of n8n : it does not have much memory, some operations are difficult to parallelize and making 10 async calls in JS with 5 workflow running didn't work for me.&lt;/p&gt;

&lt;p&gt;What worked was batching it and adding a delay. As creating embeddings and inserting in Pinecode database was a 20-30s job, I used this delay amount in my script to make sure only one workflow was running at a time.&lt;/p&gt;

&lt;p&gt;Even with this, some failed so I decided to record which index of the batches was failing to retry later in a specific retry mode in my script. Also, because of the delay, handling 282 batches of 10 elements was quite long so I also decided to store what should be the next batch to start if I had to stop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/saint-james-fr/batch-json-email-n8n" rel="noopener noreferrer"&gt;I won't bother you with the code here, just take it from this repository and make it yours - all informations are also resumed on the README.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a few hours....all newsletters are indexed. My agent was ready !&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Test &amp;amp; Use&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Let’s see it in action.&lt;/p&gt;

&lt;p&gt;Query:  &lt;em&gt;“Do you have any ressources about building components in Vue.js”&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc5w17jpp0addfymr1z00.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%2Fc5w17jpp0addfymr1z00.png" alt="Image description" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;→ GPT scans the vector DB and responds with summarized insights, linked to the source newsletter and sender.&lt;/p&gt;

&lt;p&gt;If you did the optional part, you saw that n8n's cloud instance has memory and parallelism limits. But it’s manageable...&lt;a href="https://docs.n8n.io/hosting/scaling/memory-errors/#typical-causes" rel="noopener noreferrer"&gt;and they even give you advises for this.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next step for me would probably be to self-host n8n on a VPS to have more RAM and CPU available hat as it'a possibility offered by them and that seems quite well documented. It was my first try with this tool and vectorized database and I must admit this is amazingly powerful. Can't wait to try other scenarios....&lt;/p&gt;




&lt;p&gt;I hope this tutorial was helpful! As I said at the start — I’m still learning in both the no-code and AI space, and if I can build this, you definitely can too. I'm coming from a code background so using these tools requires me to adopt a specific logic but after this first step, I must admit I loved the experience.&lt;/p&gt;

&lt;p&gt;Also, if I had to code this from scratch, I wouldn’t be writing this article — I'd still be debugging regex filters 😄&lt;/p&gt;

&lt;p&gt;Please reach out if you have feedback or ideas on how to improve the flow or integrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: My favorite newsletters
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;ByteByteGo&lt;/li&gt;
&lt;li&gt;Node Weekly&lt;/li&gt;
&lt;li&gt;Javascript Weekly&lt;/li&gt;
&lt;li&gt;Tyler from ui.dev&lt;/li&gt;
&lt;li&gt;TLDR / TLDR Web Dev&lt;/li&gt;
&lt;li&gt;Neo Kim&lt;/li&gt;
&lt;li&gt;Frontend Focus&lt;/li&gt;
&lt;li&gt;Smashing Magazine&lt;/li&gt;
&lt;li&gt;Weekly Vue News&lt;/li&gt;
&lt;li&gt;Codepen&lt;/li&gt;
&lt;li&gt;WizardZines / Julia Evans&lt;/li&gt;
&lt;li&gt;Trevor I Lasn&lt;/li&gt;
&lt;li&gt;Quastor&lt;/li&gt;
&lt;li&gt;Modern CSS Newletter&lt;/li&gt;
&lt;li&gt;Sequoia&lt;/li&gt;
&lt;li&gt;Engineering Leadership&lt;/li&gt;
&lt;li&gt;Dev Community Newsletter&lt;/li&gt;
&lt;li&gt;pganalyze&lt;/li&gt;
&lt;li&gt;The New Stack&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>n8n</category>
      <category>nocode</category>
      <category>pinecone</category>
      <category>ai</category>
    </item>
    <item>
      <title>Integrate Strapi in Rails or how I decided to build my first gem strapi_ruby</title>
      <dc:creator>saint-james-fr</dc:creator>
      <pubDate>Fri, 20 Oct 2023 09:47:06 +0000</pubDate>
      <link>https://dev.to/saintjamesfr/integrate-strapi-in-rails-or-how-i-decided-to-build-my-first-gem-strapiruby-k5b</link>
      <guid>https://dev.to/saintjamesfr/integrate-strapi-in-rails-or-how-i-decided-to-build-my-first-gem-strapiruby-k5b</guid>
      <description>&lt;h4&gt;
  
  
  TL;DR:
&lt;/h4&gt;

&lt;p&gt;This article introduces the &lt;code&gt;strapi_ruby&lt;/code&gt; gem, a solution for integrating headless CMS Strapi with Ruby on Rails seamlessly. Strapi is an ideal choice for a CMS, but using it effectively in a Rails environment and keeping your code DRY can be tricky. If you're a Rails developer willing to use Strapi, this is for you!&lt;/p&gt;

&lt;h3&gt;
  
  
  Strapi: modern, easy and flexible
&lt;/h3&gt;

&lt;p&gt;I came across a problem. I'm a Fullstack developer and I have worked with Ruby On Rails since a year, and despite the joy I try to transfer to my clients for using Rails, I often heard the need for a CMS like Wordpress. In a world dominated by content strategies, articles, videos, tutorials or interviews often lay the foundation for better referencing within a business.&lt;/p&gt;

&lt;p&gt;In the Rails world, some gems exist but nothing actually gets high enough to compare to the Wordpress standards needed by the client or really combine in Rails.&lt;/p&gt;

&lt;p&gt;I discovered the Strapi headless CMS solution and I just loved how good it is. Users and developers get a proper UI, lot of tweaking possibilities and nobody will mess up some Wordpress configuration or introduce nasty plugins, or worse, forget to update Wordpress, theme and plugins. Forget about all that.&lt;/p&gt;

&lt;p&gt;Using a headless solution means end-users will have a separate interface to manage content, and actually, that's what everybody wants as a real-world solution.&lt;/p&gt;

&lt;p&gt;Using Strapi in a Rails environment means you decided to deliver the content using a Server-Side Rendering strategy - which means having your Rails controller prepare the articles to the views. This could be a better SEO strategy than fetching it client-side with Javascript. But this is clearly post-rationalisation: I was doing front-end for a week now on the project and I wanted to write some Ruby.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's get motivated
&lt;/h3&gt;

&lt;p&gt;So, that's it, let's use Strapi. Having in mind some vague architectural concepts, I coded my best - which was not that good - and tried to have classes and modules organized but this ended up a bit too close to the needs of the client and not enough modular.&lt;/p&gt;

&lt;p&gt;Looking at the code six months later and waiting for work to show up, I decided to externalise the service and its ideas into a gem, maybe influenced by the Hacktoberfest which is happening right now. I love the idea that freelance status also offers more time to participate in Open-Source projects or build stuff for the community.&lt;/p&gt;

&lt;p&gt;I set up some objectives like having an API as clean and smooth as possible, writing tests, better organizing my code, and of course offering all options offered by Strapi REST API parameters.&lt;/p&gt;

&lt;p&gt;I did love the rush of dopamine when starting the project but sooner or later, you have to make choices about your project. I set myself a deadline of 6 days and I must say, the final form and architecture became good enough at day 3. It helped me understand a few things :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You don't have to get it right immediately. It's okay to change the design if you &lt;strong&gt;see&lt;/strong&gt; what could go wrong. &lt;em&gt;Better late than never!&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;I thought it was an amazing thing to extract some code you wrote months before you can see how you improved and that's motivating - &lt;em&gt;and it's for the good cause.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Looking for inspiration, I did check some gems repos (not related to Strapi), and also some gems that would interact with Strapi and Jekyll. I would highly recommend this if you wanna start to build a gem. Just open one and see how it's set up. Of course, I often heard this advice but I thought it was really interesting to do it &lt;em&gt;while building a gem&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Back to the gem
&lt;/h3&gt;

&lt;p&gt;Anyway created the &lt;strong&gt;strapi_ruby&lt;/strong&gt; gem, so it could be as easy as passing options to a simple API.&lt;/p&gt;

&lt;p&gt;Also, I wanted my gem to even more ease the situation by converting what selected fields from Markdown to HTML or converting ISO formatted time to regular Ruby DateTime instances.&lt;/p&gt;

&lt;p&gt;So let's dive in!&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;First, we need to download, install and configure the gem&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s1"&gt;'strapi_ruby'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle
bundle &lt;span class="nb"&gt;exec &lt;/span&gt;rake strapi_ruby:config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will generate a config file in an initializer. Fill your Strapi server URI and token, using environment variables or Rails credentials.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializer/strapi_ruby.rb&lt;/span&gt;

&lt;span class="no"&gt;StrapiRuby&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strapi_server_uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"STRAPI_SERVER_URI"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strapi_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"STRAPI_SERVER_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it, I'm ready to fetch some data! Let's get some articles for our home page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetching articles collection
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages_controller.rb&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;home&lt;/span&gt;
&lt;span class="vi"&gt;@articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;StrapiRuby&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="ss"&gt;resource: :articles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we get is an &lt;code&gt;OpenStruct&lt;/code&gt; instance similar to the JSON answer of Strapi, you can access &lt;code&gt;data&lt;/code&gt; by dot notation. Each sub fields have also been converted to Openstruct so you can navigate easily.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;# home.html.erb

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Gracefully degrading when errors happen
&lt;/h3&gt;

&lt;p&gt;But we'd rather access these in our views. And there's a simple reason for that. Let's say you reference a specific article and this one is deleted. This will trigger an StrapiRuby::ClientError and you don't want this to block your application. We need to escape an empty answer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;# home.html.erb

&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="no"&gt;StrapiRuby&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;escape_empty_answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@articles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="vi"&gt;@articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The block will return an empty string and the error will be logged in the console if there is any error.&lt;/p&gt;

&lt;p&gt;Handling errors should be an essential part of your gem if you know it will trigger runtime exceptions. I found it to be almost half of the job, especially in the last days of my challenge.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging
&lt;/h3&gt;

&lt;p&gt;Same thing about debugging. This is what we do every day at some point.&lt;/p&gt;

&lt;p&gt;Outside the block, you can still access the answer and get information about the error. Imagine requesting a non-existing resource because you typoed it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pages_controller.rb&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;home&lt;/span&gt;
&lt;span class="vi"&gt;@articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;StrapiRuby&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="ss"&gt;resource: :articless&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="vi"&gt;@articles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;span class="vi"&gt;@articleS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;meta&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; nil&lt;/span&gt;
&lt;span class="vi"&gt;@articles&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "The Strapi servers answer with an error status 404. Not Found"&lt;/span&gt;
&lt;span class="vi"&gt;@article&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endpoint&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; "URL_OF_MY_SERVER/articless"&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what happens in the gem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Begin&lt;/span&gt;
&lt;span class="c1"&gt;# some code for getting the answer ...&lt;/span&gt;
&lt;span class="no"&gt;Rescue&lt;/span&gt; &lt;span class="no"&gt;ClientError&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;# return OpenStruct.new(data: nil, meta: nil, error: OpenStruct.new(message: e.message), endpoint: @endpoint)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What do you think about this solution? I would love to know more how someone else would handle this and the graceful degradation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build queries with ease
&lt;/h2&gt;

&lt;p&gt;You can access all Strapi V4 parameters jut as populate, fields, sort, filters, locale, and pagination...&lt;/p&gt;

&lt;p&gt;Using it in Strapi with Javascript is easy enough using Strapi interactive query builder. Or have a look at the Strapi excellent documentation.&lt;/p&gt;

&lt;p&gt;Strapi use the &lt;code&gt;qs&lt;/code&gt; javascript library which traverses a JS object to build a query. &lt;code&gt;strapi_ruby&lt;/code&gt; makes it as easy with an equivalent function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Using $in operator to match multiples values&lt;/span&gt;
&lt;span class="no"&gt;StrapiRuby&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="ss"&gt;resource: :restaurants&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="ss"&gt;populate: &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="ss"&gt;filters: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                 &lt;span class="ss"&gt;id: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                   &lt;span class="s2"&gt;"$in"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"8"&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="c1"&gt;# StrapiRuby.endpoint&lt;/span&gt;
&lt;span class="s2"&gt;"YOUR_SERVER/restaurants?populate=*&amp;amp;filters[id][$in][0]=3&amp;amp;filters[id][$in][1]=6&amp;amp;filters[id][$in][2]=8"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you'd expect, some JS syntax must be translated into Ruby though.&lt;br&gt;
We need to use &lt;code&gt;strings&lt;/code&gt; or &lt;code&gt;symbols&lt;/code&gt; as values, we can pack them into &lt;code&gt;arrays&lt;/code&gt; that's no issue. For operators of filters, we need to pass them as strings because of the &lt;code&gt;$&lt;/code&gt; character.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Complex filtering with $and and $or&lt;/span&gt;
&lt;span class="no"&gt;StrapiRuby&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="ss"&gt;resource: :books&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="ss"&gt;filters: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                 &lt;span class="s2"&gt;"$or"&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;span class="ss"&gt;date: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                       &lt;span class="s2"&gt;"$eq"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"2020-01-01"&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="ss"&gt;date: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                       &lt;span class="s2"&gt;"$eq"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"2020-01-02"&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="ss"&gt;author: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                   &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                     &lt;span class="s2"&gt;"$eq"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"Kai doe"&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="c1"&gt;# StrapiRuby.endpoint&lt;/span&gt;
&lt;span class="s2"&gt;"YOUR_SERVER/books?filters[$or][0][date][$eq]=2020-01-01&amp;amp;filters[$or][1][date][$eq]=2020-01-02&amp;amp;filters[author][name][$eq]=Kai%20doe"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Convert Markdown to HTML
&lt;/h3&gt;

&lt;p&gt;Strapi is transferring Rich Content as Markdown so you can fully leverage its CMS potential. Convert with ease the fields you want using this option in the config file or in your options.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This way&lt;/span&gt;
&lt;span class="no"&gt;StrapiRuby&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert_to_html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:body&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# Or this way&lt;/span&gt;

&lt;span class="no"&gt;StrapiRuby&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="ss"&gt;resouce: :restaurants&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;convert_to_html: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:menu&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:description&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Let's consider a few scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Blog Websites: For a blog site built with Ruby on Rails, using Strapi as the content management system is a wise choice. With strapi_ruby, you can quickly fetch, render, and display blog posts in a Rails application while keeping your codebase clean.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;E-commerce Stores: In e-commerce applications, product details and descriptions are critical. The gem simplifies the retrieval of product information from Strapi, ensuring an up-to-date and error-free product catalogue.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Actually, any services that need separation from content and views. Let the creators create content!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;I hope you found this article informative and that it encourages you to build yourself your own gem or participate in Open-Source projects.&lt;/p&gt;

&lt;p&gt;Don't hesitate to visit the repo for the full code, I'd love some advice. Feel free to connect, comment, and help me improve as you just read my first article - and this was my first gem!&lt;/p&gt;

&lt;p&gt;Thank you for reading, and happy coding 😎&lt;/p&gt;

</description>
      <category>rails</category>
      <category>strapi</category>
      <category>gem</category>
      <category>ruby</category>
    </item>
  </channel>
</rss>
