<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: muvengei.dev</title>
    <description>The latest articles on Forem by muvengei.dev (@geraldmuvengei06).</description>
    <link>https://forem.com/geraldmuvengei06</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%2F577043%2F8bb582f4-8bab-48f2-826d-03d53173133d.png</url>
      <title>Forem: muvengei.dev</title>
      <link>https://forem.com/geraldmuvengei06</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/geraldmuvengei06"/>
    <language>en</language>
    <item>
      <title>I Built Daftari - A Digital Ledger for Solopreneurs to Track Job Cards and Payments via WhatApp Chats</title>
      <dc:creator>muvengei.dev</dc:creator>
      <pubDate>Wed, 04 Mar 2026 07:27:17 +0000</pubDate>
      <link>https://forem.com/geraldmuvengei06/i-built-daftari-a-digital-ledger-for-solopreneurs-to-track-job-cards-and-payments-via-whatapp-1jnp</link>
      <guid>https://forem.com/geraldmuvengei06/i-built-daftari-a-digital-ledger-for-solopreneurs-to-track-job-cards-and-payments-via-whatapp-1jnp</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/mlh-built-with-google-gemini-02-25-26"&gt;Built with Google Gemini: Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/dagufTsIKt4"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built with Google Gemini
&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%2Fbogs84gbd61m0930t5p3.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%2Fbogs84gbd61m0930t5p3.PNG" alt="Daftari AI Login"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Kenya, the local economy run on WhatsApp. From small-scale digital printing services to hair stylists, business is managed through a chaotic flow of phone calls, Sheng (local slang) texts, WhatsApp messages, and M-Pesa payment SMS.&lt;/p&gt;

&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;p&gt;Solopreneurs lose thousands of shillings every month due to poor record keeping - they forget to record a debt, or lose track of a job hidden in a chat thread. Sometimes they can get a hold of a previous quote given to a repeat customer. Manual bookkeeping is too slow for the "hustle"(fast paced business operations in Nairobi), and traditional accounting apps fell too corporate and complex.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Daftari AI (Swahili for Ledger)
&lt;/h3&gt;

&lt;p&gt;Daftari AI (Swahili for Ledger) is a WhatsApp-native business engine designed to bridge the gap between informal record-keeping and professional financial management. It eliminates the need for complex accounting software by meeting African solopreneurs where they already are: WhatsApp.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Technical Flow: How it Works&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The User Interface: The business owner sends a natural language message (in English, Swahili, or Sheng) to the Daftari AI WhatsApp bot.&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%2Ff04kvi1vkckf34du0wg5.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%2Ff04kvi1vkckf34du0wg5.PNG" alt="Chat with Daftari AI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Proxy Layer: The message is received by the WhatsApp Cloud API, which triggers a webhook to our Next.js Proxy Server. This proxy acts as the traffic controller, sanitizing the incoming payload.&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%2F1aytdrtvn1ks6ecc4kkd.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%2F1aytdrtvn1ks6ecc4kkd.PNG" alt="Example Chat"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Cognitive Engine (Google Gemini): The proxy forwards the raw, unstructured text to Google Gemini. Serving as the Cognitive Parser, Gemini analyzes the intent and context.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input&lt;/strong&gt;: "Nimefanya kazi ya Jane leo, ameacha deposit ya 500"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Process&lt;/strong&gt;: Gemini identifies the Customer (Jane), the Transaction Type (Income/Job), the Total Amount, and the Payment Status (Deposit).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output&lt;/strong&gt;: Gemini returns a Structured JSON Object of customer, transaction and job card.&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%2F1aytdrtvn1ks6ecc4kkd.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%2F1aytdrtvn1ks6ecc4kkd.PNG" alt="Example Chat"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Persistence Layer: The proxy receives this JSON and automatically updates the Postgres Database (Supabase). The user receives an instant confirmation on WhatsApp—all without ever leaving the chat.&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%2F92fthrvp0d11e9xj57jd.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%2F92fthrvp0d11e9xj57jd.PNG" alt="Daftari AI Customers"&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%2Fn5g7lp5trrmz904kr9u8.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%2Fn5g7lp5trrmz904kr9u8.PNG" alt="Daftari AI jobs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-Operation Capabilities&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Daftari AI isn't just a logger; it's a full-service business assistant. Users can perform diverse operations via simple chat commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;🛠 Job Tracking: Record new services, quantities, and descriptions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;👤 Debt Management: Instantly check "who owes what" (e.g., "Bal Jane").&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;💳 Payment Logging: Forward M-Pesa SMS messages directly to the bot to reconcile accounts instantly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📊 Quick Summaries: Request daily or weekly financial overviews (e.g., "Deni za leo" or "Muhtasari").&lt;/p&gt;&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%2Fdhcyxm4cz80updxv5u5p.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%2Fdhcyxm4cz80updxv5u5p.PNG" alt="All Operations"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Comprehensive Dashboard&lt;/strong&gt;&lt;br&gt;
While the WhatsApp bot handles the "on-the-go" hustle, Daftari AI provides a Professional Web Portal. Users can log in to their dashboard to view deep-dive analytics, export comprehensive financial reports, and manage their customer database with a birds-eye view of their business health.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;







&lt;p&gt;Daftari AI is built with a Next.js backend, Supabase for the ledger, and the WhatsApp Cloud API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;GitHub Repository: &lt;a href="https://github.com/geraldmuvengei06/daftari" rel="noopener noreferrer"&gt;https://github.com/geraldmuvengei06/daftari&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Live Demo: &lt;a href="https://daftariai.vercel.app" rel="noopener noreferrer"&gt;https://daftariai.vercel.app&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to get started&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Visit &lt;a href="https://daftariai.vercel.app" rel="noopener noreferrer"&gt;https://daftariai.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click Get Started with WhatsApp&lt;/li&gt;
&lt;li&gt;Start chatting with the WhatsApp bot&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;To login to the web portal&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;send "Login" to the bot&lt;/li&gt;
&lt;li&gt;Provide your email
&lt;em&gt;Bot registers your email and sends a email verification link&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Click the email verification link, you will be redirected to your dashboard. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/dagufTsIKt4"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;Caption: Gemini parsing an M-Pesa SMS and a Sheng job descriptions. The flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;This project was a masterclass in Technical Pivot Strategy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Model Versatility: I learned that "newer isn't always easier." When I hit regional quota blocks with Gemini 2.0, I had to learn how to reconfigure my middleware to leverage Gemini 1.5 Flash, which provided the stability and rate limits I needed for a production-ready bot.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The "Zero UI" Philosophy: I learned that the best interface for a busy entrepreneur is the one they already use. By building inside WhatsApp, I eliminated the "app fatigue" that kills most small-business tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prompt Engineering for Chaos: I spent hours refining system instructions so Gemini could understand Sheng and mixed-language context, ensuring it never missed a decimal point in a transaction.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Google Gemini Feedback
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Good&lt;/strong&gt;&lt;br&gt;
The contextual intelligence of Gemini 1.5 Flash is world-class. It didn't just parse text; it understood intent. Whether a user forwarded a formal bank message or a casual "Jane paid me," Gemini treated the data with 100% accuracy. The speed of the Flash model is perfect for real-time WhatsApp webhooks—responses feel instant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Bad&lt;/strong&gt;&lt;br&gt;
I encountered significant regional friction regarding Google Cloud Billing. In Kenya, setting up a verified billing account for "production" tier models often results in cryptic errors (like the [OR-CBAT-23] code), which can be a massive barrier for developers in the Global South.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Ugly (and how I fixed it)&lt;/strong&gt;&lt;br&gt;
During the heat of development, I hit a 429 "Limit 0" Quota Error on the Gemini 2.0 API. Google billing kept rejecting my payment method. This could have ended the project. Instead of stopping, I pivoted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I shifted my backend to Gemini 1.5 Flash via AI Studio to bypass the billing lock, which also didn't work so I added OPENAI as a fallback&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I troubleshooted a Meta App Conflict where two apps were fighting for the same phone number.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I automated the WhatsApp Registration Handshake using cURL to reduce the user registration process.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;: Daftari AI is more than a bot; it's a tool that could revolutionize bookkeeping by meeting users where they are - WhatsApp.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>geminireflections</category>
      <category>gemini</category>
      <category>ai</category>
    </item>
    <item>
      <title>I built ResumeTarget AI - A tool to help you tailor your resume to a specific job in minutes</title>
      <dc:creator>muvengei.dev</dc:creator>
      <pubDate>Sat, 14 Feb 2026 22:33:06 +0000</pubDate>
      <link>https://forem.com/geraldmuvengei06/i-built-resumetarget-ai-a-tool-to-help-you-tailor-your-resume-to-a-specific-job-in-minutes-375f</link>
      <guid>https://forem.com/geraldmuvengei06/i-built-resumetarget-ai-a-tool-to-help-you-tailor-your-resume-to-a-specific-job-in-minutes-375f</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-01-21"&gt;GitHub Copilot CLI Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://resumetargetai.com" rel="noopener noreferrer"&gt;ResumeTarget AI&lt;/a&gt; - A tool to help you tailor your resume to a specific job in minutes. With the help of Copilot CLI, I built an entire Next.js, FastAPI. Postgres MVP and deployed it to EC2 with Terraform!&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem Statement &amp;amp; Solution
&lt;/h2&gt;

&lt;p&gt;I had a challenge applying to tech jobs and realised I had to apply to tens, if not hundreds, before I could secure an interview. For each application, I've had to customise my résumé to the target role (as defined by the job description). This was tedious and time-consuming. I decided to build a tool that leverages AI to help me tailor my résumés quickly and effectively. I have completed building the AI résumé builder. Now I'm working on the job description matching and résumé scoring feature to help score résumés for a target job. For this, I'm using Sentence Transformers to compute cosine similarities between résumé sections and job descriptions. I'm also using LLMs for résumé suggestions. &lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
      <category>cli</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>Resolving Gmail Rejection on HestiaCP - Exim4</title>
      <dc:creator>muvengei.dev</dc:creator>
      <pubDate>Sun, 28 Sep 2025 05:15:23 +0000</pubDate>
      <link>https://forem.com/geraldmuvengei06/resolving-gmail-rejection-on-hestiacp-exim4-3116</link>
      <guid>https://forem.com/geraldmuvengei06/resolving-gmail-rejection-on-hestiacp-exim4-3116</guid>
      <description>&lt;h2&gt;
  
  
  Resolving Gmail Rejection on HestiaCP (Exim4)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Issue
&lt;/h3&gt;

&lt;p&gt;When attempting to send emails from Gmail to a mail server hosted on EC2 with HestiaCP, the messages were being rejected with the following error:&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%2Fwjq1254b8oaq47as8dxj.gif" 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%2Fwjq1254b8oaq47as8dxj.gif" alt="Resolving Gmail Rejection on HestiaCP (Exim4)" width="256" height="192"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;550 Rejected because xxx.xx.xxx.xx(gmail's IP address) is in a black list at zen.spamhaus.org
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This happened because Exim4 was configured to block incoming messages from IPs listed on Spamhaus, which occasionally includes Gmail’s sending IPs.&lt;/p&gt;




&lt;h3&gt;
  
  
  Root Cause
&lt;/h3&gt;

&lt;p&gt;HestiaCP’s default Exim templates include a &lt;strong&gt;dnslists (RBL)&lt;/strong&gt; check against Spamhaus (&lt;code&gt;zen.spamhaus.org&lt;/code&gt;). Since Gmail’s IP addresses can sometimes appear in this list, legitimate emails were being denied.&lt;/p&gt;




&lt;h3&gt;
  
  
  Fixing the Problem
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Find the Exim configuration or template file&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The RBL rule is likely located in the Exim configuration template:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; nano /etc/exim4/exim4.conf.template
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To verify, search for the Spamhaus entry:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"zen.spamhaus.org"&lt;/span&gt; /etc/exim4/
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Modify the RBL block&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You should find a section similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   deny message = rejected because $sender_host_address is in a black list at $dnslist_domain
       dnslists = zen.spamhaus.org
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Option A – Disable completely&lt;/strong&gt;&lt;br&gt;
 Comment out the block:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; # deny message = rejected because $sender_host_address is in a black list at $dnslist_domain
 #     dnslists = zen.spamhaus.org
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Option B – Change to warnings&lt;/strong&gt;&lt;br&gt;
 Replace &lt;code&gt;deny&lt;/code&gt; with &lt;code&gt;warn&lt;/code&gt; so suspicious emails are flagged but not blocked:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; warn dnslists = zen.spamhaus.org
      add_header = X-RBL-Warning: $sender_host_address is listed at $dnslist_domain
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Rebuild and restart Exim&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Apply the changes with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   update-exim4.conf
   systemctl restart exim4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Gmail messages can now be delivered successfully to your EC2 mail server.&lt;/li&gt;
&lt;li&gt;Exim no longer blocks Gmail IPs just because they appear in Spamhaus.&lt;/li&gt;
&lt;li&gt;If you use the &lt;code&gt;warn&lt;/code&gt; approach, questionable emails are tagged rather than rejected.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Additional Notes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;These changes only affect &lt;strong&gt;inbound mail filtering&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Outbound delivery still requires correct DNS configuration (PTR, SPF, DKIM, DMARC) and may also need AWS SMTP unblocking or an SES relay.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notes: Leaving this here for future reference. Ps ✌️&lt;/p&gt;

</description>
      <category>ec2</category>
      <category>hestiacp</category>
      <category>cloudcomputing</category>
      <category>aws</category>
    </item>
    <item>
      <title>The Case of the Frozen Supabase/Prisma db push: Why Your Migrations Might Be Stuck</title>
      <dc:creator>muvengei.dev</dc:creator>
      <pubDate>Wed, 11 Jun 2025 16:06:13 +0000</pubDate>
      <link>https://forem.com/geraldmuvengei06/the-case-of-the-frozen-supabaseprisma-db-push-why-your-migrations-might-be-stuck-75j</link>
      <guid>https://forem.com/geraldmuvengei06/the-case-of-the-frozen-supabaseprisma-db-push-why-your-migrations-might-be-stuck-75j</guid>
      <description>&lt;h2&gt;
  
  
  Frozen Prisma &lt;code&gt;db push&lt;/code&gt;: Why Your Migrations Might Be Stuck
&lt;/h2&gt;

&lt;p&gt;Have you ever run &lt;code&gt;npx prisma db push&lt;/code&gt; and watched it... do nothing? It just freezes, leaving you wondering if your database is giving you the silent treatment. If you've been working with Prisma and Supabase, you've likely encountered this exact scenario.&lt;/p&gt;

&lt;p&gt;I recently ran into this issue while trying to push a my prisma schema:&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;# Regenerate client&lt;/span&gt;
npx prisma generate

&lt;span class="c"&gt;# Attempting to push the schema&lt;/span&gt;
npx prisma db push &lt;span class="nt"&gt;--force-reset&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The commands just hung, never completing successfully. A simple task becoming a frustrating blocker.&lt;/p&gt;




&lt;h2&gt;
  
  
  My findings: Direct Connections for Migrations
&lt;/h2&gt;

&lt;p&gt;After some digging, I realized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DIRECT_URL&lt;/code&gt;&lt;/strong&gt;: This is your direct, unpooled connection to the Supabase database. &lt;strong&gt;This is what Prisma needs for database migrations&lt;/strong&gt; and schema pushes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DATABASE_URL&lt;/code&gt;&lt;/strong&gt;: This is your connection string that goes through Supabase's connection pooler (Supavisor). This is ideal for your application's runtime queries, as it efficiently manages connections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you instruct Prisma to modify your database schema (such as with &lt;code&gt;db push&lt;/code&gt; or &lt;code&gt;migrate&lt;/code&gt;), it requires a direct, unmediated connection to the database. The connection pooler, while great for application traffic, can sometimes interfere with these administrative operations.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix: Configure &lt;code&gt;directUrl&lt;/code&gt; in Your Prisma Schema
&lt;/h2&gt;

&lt;p&gt;The solution is straightforward: ensure your &lt;code&gt;datasource&lt;/code&gt; block in &lt;code&gt;schema.prisma&lt;/code&gt; explicitly defines &lt;code&gt;directUrl&lt;/code&gt; and points it to your unpooled Supabase connection string.&lt;/p&gt;

&lt;p&gt;Here's how your &lt;code&gt;schema.prisma&lt;/code&gt; should look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;generator&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;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;prisma-client-js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;datasource&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postgresql&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;directUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DIRECT_URL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Essential for migrations!&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By adding &lt;code&gt;directUrl = env("DIRECT_URL")&lt;/code&gt;, you're telling Prisma which connection to use for schema modifications. Ensure that your &lt;code&gt;DIRECT_URL&lt;/code&gt; environment variable is correctly set in your development environment and any deployment pipelines.&lt;/p&gt;

&lt;p&gt;This small but configuration change ensures your Prisma migrations run smoothly, preventing those frustrating freezes so that you can get back to writing your app.&lt;/p&gt;




&lt;p&gt;What blocker have you faced while working with ORMs?&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>prisma</category>
      <category>database</category>
      <category>nuxt</category>
    </item>
    <item>
      <title>Personalized E-Learning Recommendation Agent.ai Based on Skills, Certification, Education, Experience Gaps 🧑‍💻📚</title>
      <dc:creator>muvengei.dev</dc:creator>
      <pubDate>Sun, 26 Jan 2025 21:19:48 +0000</pubDate>
      <link>https://forem.com/geraldmuvengei06/personalized-e-learning-recommendation-agentai-based-on-skills-certification-education-3dda</link>
      <guid>https://forem.com/geraldmuvengei06/personalized-e-learning-recommendation-agentai-based-on-skills-certification-education-3dda</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://srv.buysellads.com/ads/long/x/T6EK3TDFTTTTTT6WWB6C5TTTTTTGBRAPKATTTTTTWTFVT7YTTTTTTKPPKJFH4LJNPYYNNSZL2QLCE2DPPQVCEI45GHBT" rel="noopener noreferrer"&gt;Agent.ai&lt;/a&gt; Challenge: Full-Stack Agent (&lt;a href="https://dev.to/challenges/agentai"&gt;See Details&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built an intelligent agent that recommends tailored learning paths based on a user's career interests matched with gaps in skills, education, certifications, technical proficiencies  by integrating real-time course data from multiple online learning platforms (e.g., Coursera, Udemy). &lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem.
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;I am a web developer with an explorer spirit. I like to explore new technologies, and use them to bring ideas into digital reality. 🧑‍💻🚀&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I spent countless hours trying to look for gaps in my skills and the best learning paths. Sometimes going from tutorial to another (tutorial hell) hence which wastes valuable time. &lt;/p&gt;

&lt;p&gt;A beginner web developer came to me the other day asking me to recommend the best way to learn and share useful courses. I spend an hour preparing a good study guide together with online courses. Thanks to my agent, this process takes less than a minute.&lt;/p&gt;

&lt;h3&gt;
  
  
  The solution
&lt;/h3&gt;

&lt;p&gt;An intelligent agent that recommends personalized learning paths and specific courses by aligning a user's career interests with their gaps in skills, education, certifications, and technical proficiencies. By integrating real-time course data from multiple online learning platforms like Coursera and Udemy, it crafts tailored learning journeys that bridge those gaps and accelerate the user's professional growth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Try out my agent on this &lt;a href="https://agent.ai/agent/b072ab8yuyqmg3dl" rel="noopener noreferrer"&gt;link&lt;/a&gt; or this &lt;a href="https://agent.ai/agent/e-learning-recommendation-agent" rel="noopener noreferrer"&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1&amp;gt;&amp;gt; Provide your key career interest. e.g. Full-stack developer, Data scientist&lt;/strong&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%2Fhi92g8e8b1jxjvl5xmgj.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%2Fhi92g8e8b1jxjvl5xmgj.png" alt="Provide your key career interest. e.g. Full-stack developer, Data scientist" width="800" height="557"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2&amp;gt;&amp;gt; Upload your resume&lt;/strong&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%2Feh0j6bdeehfka4onrsbh.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%2Feh0j6bdeehfka4onrsbh.png" alt="Upload your resume" width="800" height="657"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3&amp;gt;&amp;gt; Select the type of gaps to identify&lt;/strong&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%2Fmod66xu7fmwdw45rvl6e.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%2Fmod66xu7fmwdw45rvl6e.png" alt="Select the type of gaps to identify&amp;lt;br&amp;gt;
" width="800" height="632"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4&amp;gt;&amp;gt; Get your recommendations.&lt;/strong&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%2F8x4nqfd31ib68nvyy65i.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%2F8x4nqfd31ib68nvyy65i.png" alt=" Get your recommendations" width="800" height="656"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;I used agent.at agent to collect the user info. i.e. career interests, resume, gaps to identify. Using an LLM model (best is Claude &amp;amp; GPT 4) I use the provided data to identify gaps in the format:-&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="err"&gt;skill_gaps:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'javascript'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;experience_gaps:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;'python'&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="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the data above, I call my lambda function (which refused to deploy on agent.ai 😔).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UDEMY_API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.udemy.com/api-2.0/courses&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;COURSERA_API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.coursera.org/api/courses.v1?q=search&amp;amp;query=&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;EDX_API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.edx.org/api/v1/catalog/search?q=&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;UDEMY_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_UDEMY_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Store securely (e.g., AWS Secrets Manager)&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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;skillGaps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;skill_gaps&lt;/span&gt; &lt;span class="o"&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;experienceGaps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;experience_gaps&lt;/span&gt; &lt;span class="o"&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;skillCourses&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;fetchCourses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;skillGaps&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;experienceCourses&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;fetchCourses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;experienceGaps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;skill_gap_courses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;skillCourses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;experience_gap_courses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;experienceCourses&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error fetching courses:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to fetch courses&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchUdemyCourses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;UDEMY_API_URL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?search=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;UDEMY_API_KEY&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Varies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://www.udemy.com&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;course&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="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Udemy fetch error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&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;fetchCourseraCourses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;COURSERA_API_URL&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;keyword&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;data&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;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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;course&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="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Free or Paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Varies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://www.coursera.org/learn/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Coursera fetch error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&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;fetchEdxCourses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;EDX_API_URL&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;keyword&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;data&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;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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Free or Paid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Varies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;course&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;marketing_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;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;edX fetch error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&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;fetchCourses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;courseResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyword&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;keywords&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;udemyCourses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;courseraCourses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;edxCourses&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nf"&gt;fetchUdemyCourses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;fetchCourseraCourses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;fetchEdxCourses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyword&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="nx"&gt;courseResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;udemyCourses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;courseraCourses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;edxCourses&lt;/span&gt;&lt;span class="p"&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;courseResults&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lambda function returns courses in the format:&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="err"&gt;skill_gap_courses:&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="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Course&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;price:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;duration:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;link:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'https://www.udemy.com/course&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Course&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;A'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;price:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Free&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Paid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;duration:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Varies'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;link:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'https://www.coursera.org/learn/course-a'&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="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Course&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;X'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;price:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Free'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;duration:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;weeks'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;link:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'https://www.edx.org/course-x'&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="err"&gt;experience_gap_courses:&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="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Course&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;price:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;duration:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;link:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'https://www.udemy.com/course&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Course&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;A'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;price:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Free&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Paid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;duration:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Varies'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;link:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'https://www.coursera.org/learn/course-a'&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="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Course&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;X'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;price:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Free'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;duration:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;weeks'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;link:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'https://www.edx.org/course-x'&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="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;Using agent.ai output, I display the end results to the user.&lt;br&gt;
Note: I'm able to generate learning recommendations using a LLM, together with the courses response.&lt;/p&gt;

&lt;p&gt;The agent.ai configuration&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%2Fdfv9tyl7aulkfqx7nhun.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%2Fdfv9tyl7aulkfqx7nhun.png" alt=" " width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Drawbacks 😔
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;I couldn't get the lambda function to deploy on agent.ai. I was getting an error&lt;/li&gt;
&lt;li&gt;Haven't found udemy open api client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Agent.ai Experience
&lt;/h2&gt;

&lt;p&gt;I was amazed at how easy it is to get a personalized agent up and running with Agent.ai. One of my takers was the ability to choose from multiple LLM engines. This was very useful since an LLM could be good for something in comparison with the others.&lt;/p&gt;

&lt;p&gt;Based on the selected career path, this agent identifies gaps in one's resume then suggests courses and study paths to achieve the missing skills. I was to leverage an AWS cloud function to query Udemy and Coursera courses APIs. Unfortunately, I couldn't find open sourced courses API and also my could function failed to deploy. Which is a challenge I faced on the &lt;a href="https://agent.ai/" rel="noopener noreferrer"&gt;Agent.ai&lt;/a&gt; platform.&lt;/p&gt;

&lt;p&gt;I had a little challenge reading from uploaded files until I came across a &lt;a href="https://www.youtube.com/watch?v=R1WdthPjDvE" rel="noopener noreferrer"&gt;YouTube video&lt;/a&gt; that suggests using "Get Data from Builder's Knowledge Base" to read files.&lt;/p&gt;

&lt;p&gt;It worked like magic. 🚀&lt;/p&gt;

&lt;p&gt;Looking forward to building more complex agents.&lt;br&gt;
Thanks for the read. 🙏&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>agentaichallenge</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>E-Learning Recommendation Agent.ai Based on Your Resume &amp; Identified Skills, Certification, Education, Experience Gaps 🧑‍💻📚</title>
      <dc:creator>muvengei.dev</dc:creator>
      <pubDate>Sun, 26 Jan 2025 21:19:43 +0000</pubDate>
      <link>https://forem.com/geraldmuvengei06/e-learning-recommendation-agentai-based-on-your-resume-identified-skills-certification-5adg</link>
      <guid>https://forem.com/geraldmuvengei06/e-learning-recommendation-agentai-based-on-your-resume-identified-skills-certification-5adg</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://srv.buysellads.com/ads/long/x/T6EK3TDFTTTTTT6WWB6C5TTTTTTGBRAPKATTTTTTWTFVT7YTTTTTTKPPKJFH4LJNPYYNNSZL2QLCE2DPPQVCEI45GHBT" rel="noopener noreferrer"&gt;Agent.ai&lt;/a&gt; Challenge: Productivity-Pro Agent (&lt;a href="https://dev.to/challenges/agentai"&gt;See Details&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built an intelligent agent that recommends tailored learning paths based on a user's career interests matched with gaps in skills, education, certifications, technical proficiencies  by integrating real-time course data from multiple online learning platforms (e.g., Coursera, Udemy). &lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem.
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;I am a web developer with an explorer spirit. I like to explore new technologies, and use them to bring ideas into digital reality. 🧑‍💻🚀&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I spent countless hours trying to look for gaps in my skills and the best learning paths. Sometimes going from tutorial to another (tutorial hell) hence which wastes valuable time. &lt;/p&gt;

&lt;p&gt;A beginner web developer came to me the other day asking me to recommend the best way to learn and share useful courses. I spend an hour preparing a good study guide together with online courses. Thanks to my agent, this process takes less than a minute.&lt;/p&gt;

&lt;h3&gt;
  
  
  The solution
&lt;/h3&gt;

&lt;p&gt;An intelligent agent that recommends personalized learning paths and specific courses by aligning a user's career interests with their gaps in skills, education, certifications, and technical proficiencies. By integrating real-time course data from multiple online learning platforms like Coursera and Udemy, it crafts tailored learning journeys that bridge those gaps and accelerate the user's professional growth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Try out my agent on this &lt;a href="https://agent.ai/agent/b072ab8yuyqmg3dl" rel="noopener noreferrer"&gt;link&lt;/a&gt; or this &lt;a href="https://agent.ai/agent/e-learning-recommendation-agent" rel="noopener noreferrer"&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1&amp;gt;&amp;gt; Provide your key career interest. e.g. Full-stack developer, Data scientist&lt;/strong&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%2Fhi92g8e8b1jxjvl5xmgj.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%2Fhi92g8e8b1jxjvl5xmgj.png" alt="Provide your key career interest. e.g. Full-stack developer, Data scientist" width="800" height="557"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2&amp;gt;&amp;gt; Upload your resume&lt;/strong&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%2Feh0j6bdeehfka4onrsbh.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%2Feh0j6bdeehfka4onrsbh.png" alt="Upload your resume" width="800" height="657"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3&amp;gt;&amp;gt; Select the type of gaps to identify&lt;/strong&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%2Fmod66xu7fmwdw45rvl6e.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%2Fmod66xu7fmwdw45rvl6e.png" alt="Select the type of gaps to identify&amp;lt;br&amp;gt;
" width="800" height="632"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4&amp;gt;&amp;gt; Get your recommendations.&lt;/strong&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%2F8x4nqfd31ib68nvyy65i.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%2F8x4nqfd31ib68nvyy65i.png" alt=" Get your recommendations" width="800" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent.ai Experience
&lt;/h2&gt;

&lt;p&gt;I was amazed at how easy it is to get a personalized agent up and running with Agent.ai. One of my takers was the ability to choose from multiple LLM engines. This was very useful since an LLM could be good for something in comparison with the others.&lt;/p&gt;

&lt;p&gt;Based on the selected career path, this agent identifies gaps in one's resume then suggests courses and study paths to achieve the missing skills. I was to leverage an AWS cloud function to query Udemy and Coursera courses APIs. Unfortunately, I couldn't find open sourced courses API and also my could function failed to deploy. Which is a challenge I faced on the &lt;a href="https://agent.ai/" rel="noopener noreferrer"&gt;Agent.ai&lt;/a&gt; platform.&lt;/p&gt;

&lt;p&gt;I had a little challenge reading from uploaded files until I came across a &lt;a href="https://www.youtube.com/watch?v=R1WdthPjDvE" rel="noopener noreferrer"&gt;YouTube video&lt;/a&gt; that suggests using "Get Data from Builder's Knowledge Base" to read files.&lt;/p&gt;

&lt;p&gt;It worked like magic. 🚀&lt;/p&gt;

&lt;p&gt;Looking forward to building more complex agents.&lt;br&gt;
Thanks for the read. 🙏&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>agentaichallenge</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Building a Nuxt.js + Vue.js SaaS Starter: Authentication, Payments, and Supabase Made Easy. A nuxt js saas starter</title>
      <dc:creator>muvengei.dev</dc:creator>
      <pubDate>Sat, 21 Sep 2024 18:16:27 +0000</pubDate>
      <link>https://forem.com/geraldmuvengei06/building-a-nuxtjs-vuejs-saas-starter-authentication-payments-and-supabase-made-easy-3d9p</link>
      <guid>https://forem.com/geraldmuvengei06/building-a-nuxtjs-vuejs-saas-starter-authentication-payments-and-supabase-made-easy-3d9p</guid>
      <description>&lt;h3&gt;
  
  
  Building a Nuxt.js + Vue.js SaaS Starter: Authentication, Payments, and Supabase Made Easy
&lt;/h3&gt;

&lt;p&gt;Hey Devs! 👋&lt;/p&gt;

&lt;p&gt;I’m excited to share a project I’ve been working on—a &lt;strong&gt;Nuxt.js + Vue.js SaaS starter&lt;/strong&gt; designed to make building and scaling SaaS apps much easier. It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NextAuth&lt;/strong&gt; for flexible authentication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stripe&lt;/strong&gt; for handling payments and subscriptions&lt;/li&gt;
&lt;li&gt;Seamless integration with &lt;strong&gt;Supabase&lt;/strong&gt; for your database and real-time needs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why I Built This
&lt;/h3&gt;

&lt;p&gt;Starting a SaaS project can be daunting, with so many moving parts—auth, payments, subscriptions, and a database connection, just to name a few. I wanted to create a starter template that takes care of these essentials so you can focus on building out the core features of your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Included
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NextAuth integration&lt;/strong&gt;: Easily manage user authentication with providers like Google, GitHub, and more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stripe integration&lt;/strong&gt;: Out-of-the-box payments and subscription handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase connection&lt;/strong&gt;: A powerful, scalable backend with minimal setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Use It?
&lt;/h3&gt;

&lt;p&gt;If you're starting a SaaS project, this starter kit will save you hours of setup and debugging time, allowing you to focus on building your product. Whether you're working on a side project or launching a new business, the goal is to give you a solid foundation that takes care of the boring parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it Out
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Check out the &lt;strong&gt;GitHub repo&lt;/strong&gt;: &lt;a href="https://github.com/geraldmuvengei06/nuxt-saas-starter/tree/master" rel="noopener noreferrer"&gt;nuxt-saas-starter&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live demo on &lt;strong&gt;Vercel&lt;/strong&gt;: &lt;a href="https://nuxt-saas-starter.vercel.app/" rel="noopener noreferrer"&gt;nuxt-saas-starter.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;I'm continuing to fine-tune the project and would love feedback from the community. If you’re interested, feel free to check it out, contribute, or offer suggestions!&lt;/p&gt;

&lt;p&gt;Let’s build something awesome! 🚀&lt;/p&gt;




&lt;p&gt;Feel free to comment below if you have any questions or ideas!&lt;/p&gt;

</description>
      <category>vue</category>
      <category>opensource</category>
      <category>saas</category>
      <category>nuxt</category>
    </item>
    <item>
      <title>How to setup nuxt-auth v6 authentication with the "credentials" provider on nuxt3 project</title>
      <dc:creator>muvengei.dev</dc:creator>
      <pubDate>Sat, 22 Jul 2023 13:09:14 +0000</pubDate>
      <link>https://forem.com/geraldmuvengei06/how-to-setup-nuxt-auth-v6-authentication-with-the-local-provider-on-nuxt3-project-3970</link>
      <guid>https://forem.com/geraldmuvengei06/how-to-setup-nuxt-auth-v6-authentication-with-the-local-provider-on-nuxt3-project-3970</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;nuxt-auth&lt;/strong&gt; is an open source Nuxt module that provides authentication for non-static Nuxt 3 applications.  - &lt;a href="https://sidebase.io/nuxt-auth/getting-started" rel="noopener noreferrer"&gt;sidebase&lt;/a&gt;&lt;br&gt;
The easiest way to get started with nuxt-auth is using the sidebase Merino stack: - &lt;a href="https://sidebase.io/nuxt-auth/getting-started" rel="noopener noreferrer"&gt;sidebase&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My Opinion:&lt;/p&gt;

&lt;p&gt;It disappoints when I have to go through the entire documentation of a module for a basic implementation. This happened to me when I was setting up nut-auth on my client’s project. Coming from Nuxt 2 to Nuxt 3 a lot had changed. And with such changes came newer Nuxt modules, most of which had a different implementation. It took me a considerable amount of time to set up nuxt-auth for the first time. I’m going to write about my implementation so that you don’t have to go through the same “struggle” as I did.&lt;/p&gt;

&lt;p&gt;A BTW: What did the senior dev say to the junior dev? 'I am the documentation!' &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLDR;&lt;/strong&gt;&lt;br&gt;
Setting up nuxt-auth with credentials provider: Quick&lt;/p&gt;

&lt;p&gt;Note: This guide assumes that your api lives in a different environment.&lt;/p&gt;

&lt;p&gt;Lets dive in.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Create a new Nuxt app.
&lt;/h2&gt;

&lt;p&gt;Open your favourite terminal, navigate to your projects folder and run this command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;npx nuxi@latest init my-app
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have already setup a nuxt project, you can skip to the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Install nuxt-auth module.
&lt;/h2&gt;

&lt;p&gt;Install nuxt-auth by running this command on the root folder of your project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;// npm
npm i -D @sidebase/nuxt-auth

// yarn
yarn add @sidebase/nuxt-auth
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this article, I used &lt;code&gt;"@sidebase/nuxt-auth": "0.6.0-beta.3",&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Add nuxt-auth to nuxt.config.ts
&lt;/h2&gt;

&lt;p&gt;Open the project on your favourite editor, preferably VSCode. &lt;br&gt;
Open nuxt.config.ts file located in the root of your project, look for the modules option and add &lt;code&gt;@sidebase/nuxt-auth&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
   &lt;span class="na"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sidebase/nuxt-auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: More configs on nuxt.config.ts
&lt;/h2&gt;

&lt;p&gt;While on the nuxt.config.ts file, at the same level as modules, add the auth configs as shown below. Each option has comments. For more options, checkout the official documentation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Auth */&lt;/span&gt;
  &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;globalAppMiddleware&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="nx"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NUXT_PUBLIC_API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/identity/accounts/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nx"&gt;signOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/identity/accounts/logout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nx"&gt;signUp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/identity/accounts/register&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="nx"&gt;getSession&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/identity/me&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/signin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;signInResponseTokenPointer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/accessToken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;sessionDataType&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="nx"&gt;enableSessionRefreshPeriodically&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;enableSessionRefreshOnWindowFocus&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="nx"&gt;globalMiddlewareOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;allow404WithoutAuth&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="c1"&gt;// Defines if the 404 page will be accessible while unauthenticated&lt;/span&gt;
      &lt;span class="nx"&gt;addDefaultCallbackUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; // Where authenticated user will be redirected to by default
    }
  }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I have explained each of the options above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;globalAppMiddleware&lt;/code&gt;&lt;/strong&gt; &lt;br&gt;
Setting this to &lt;code&gt;true&lt;/code&gt; protect all routes by default. &lt;/p&gt;

&lt;p&gt;If you want to exempt a route from the auth middleware, add this code on the page’s &lt;a href="https://nuxt.com/docs/api/utils/define-page-meta#definepagemeta" rel="noopener noreferrer"&gt;definePageMeta macro:&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;definePageMeta&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;auth&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="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want a page to be accessible only when logged out, add this to definePageMeta macro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;definePageMeta&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;unauthenticatedOnly&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="na"&gt;navigateAuthenticatedTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="err"&gt;‘&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Where the user should be redirected if not logged in&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;baseURL&lt;/code&gt;&lt;/strong&gt; - The base api url to your auth module&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;provider&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
The authentication provide. For this article, we are going to focus on the local provider. Inside here, we will define the provider type and options.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    `type: ‘local'` - We’re going to use the local provider
`endpoints` - define the authentication endpoints for:
    `signIn`
    `signOut`
    `signUp`
    `getSession` - This is the url for fetching the logged in user data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;token&lt;/code&gt;&lt;/strong&gt; &lt;br&gt;
Takes in &lt;strong&gt;&lt;code&gt;signInResponseTokenPointer&lt;/code&gt;&lt;/strong&gt; which is basically the key that contains the accessToken after a successful login.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Note: This was the most confusing part for me. *&lt;/em&gt;&lt;br&gt;
For instance, if the login response looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer &amp;lt;token&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the token option should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;signInResponseTokenPointer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/accessToken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To read more about all the possible options checkout the &lt;a href="https://sidebase.io/nuxt-auth/configuration/nuxt-config" rel="noopener noreferrer"&gt;nuxt-auth options documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Setup your signIn, signUp, signOut, getSession components
&lt;/h2&gt;

&lt;h3&gt;
  
  
  SignIn
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signIn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// uses the default signIn function provided by nuxt-auth&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reactive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="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;login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&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;res&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;signIn&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="nx"&gt;formData&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;callbackUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Where the user will be redirected after a successiful login&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;res&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;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;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;definePageMeta&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Signin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;empty&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;public&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="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;unauthenticatedOnly&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="na"&gt;navigateAuthenticatedTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;


&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"custom-bg mb-14 p-14 text-center text-white"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Sigin&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;submit.prevent=&lt;/span&gt;&lt;span class="s"&gt;"login"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card custom-card mx-auto w-11/12 max-w-md bg-white p-6 shadow-lg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"formData.email"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"email here.."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;v-model=&lt;/span&gt;&lt;span class="s"&gt;"formData.password"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sign In&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SignUp
&lt;/h3&gt;

&lt;p&gt;You can implement the signup. Its way similar to signIn. It can even be a fetch or &lt;a href="https://axios-http.com/docs/intro" rel="noopener noreferrer"&gt;axios&lt;/a&gt; request.&lt;/p&gt;

&lt;h3&gt;
  
  
  SignOut
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;_signOut&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;await&lt;/span&gt; &lt;span class="nf"&gt;signOut&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;callbackUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/account/signin/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;redirect&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="c1"&gt;// Add after logout logic here, could be removing the session data from localstorage or whatever&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;life&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&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="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"_signOut()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   Sign Out
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  getSession
&lt;/h3&gt;

&lt;p&gt;Add a home page. This will be protected by default by the &lt;strong&gt;globalAppMiddleware&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nf"&gt;definePageMeta&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;public&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getSession&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// data - authenticated user data returned from getSession option that we setup on nuxt.config.ts&lt;/span&gt;
&lt;span class="c1"&gt;// status - unauthenticated | authenticated&lt;/span&gt;
&lt;span class="c1"&gt;// getSession - call this function asynchronusly to get the session data. i.e. The authenticated user data&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col items-start justify-center p-5 sm:p-14"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-white text-2xl md:text-4xl  mb-5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      Hello, 
      &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"status === 'authenticated'"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;last_name&lt;/span&gt; &lt;span class="si"&gt;}}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-white opacity-60"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Welcome back&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Logout&amp;gt;&amp;lt;/Logout&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to run the project
&lt;/h2&gt;

&lt;p&gt;Add a &lt;code&gt;.env&lt;/code&gt; file at the root of your project and add this config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NUXT_PUBLIC_API_URL=&amp;lt;your-api-endpoint&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start a dev server by running this command in terminal at the root of your project:&lt;br&gt;
&lt;code&gt;npm run dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The project should now be accessible at:&lt;br&gt;
&lt;code&gt;http://localhost:3000&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Findings
&lt;/h2&gt;

&lt;p&gt;If you navigate to &lt;code&gt;/home&lt;/code&gt; you will be redirected to &lt;code&gt;/signin&lt;/code&gt; since the route is protected.&lt;/p&gt;

&lt;p&gt;Navigating to &lt;code&gt;/&lt;/code&gt; works when authenticated or unauthenticated&lt;/p&gt;

&lt;p&gt;Navigating to &lt;code&gt;/sigin&lt;/code&gt; while authenticated should redirect you to &lt;code&gt;/home&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Clicking &lt;code&gt;Logout&lt;/code&gt; button while at &lt;code&gt;/home&lt;/code&gt; should log you out, and redirect you to &lt;code&gt;/signin&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;If you have followed these steps, you should have a working nuxt-auth implementation! You can find the complete project on &lt;a href="https://github.com/geraldmuvengei06/nuxt-3-auth-credentials" rel="noopener noreferrer"&gt;Github&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>nuxtauth</category>
      <category>vue</category>
      <category>nuxt</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Is Continuous Learning Helping your Web Development Career?</title>
      <dc:creator>muvengei.dev</dc:creator>
      <pubDate>Wed, 24 Nov 2021 14:09:58 +0000</pubDate>
      <link>https://forem.com/geraldmuvengei06/is-continuous-learning-helping-your-web-development-career-2bc5</link>
      <guid>https://forem.com/geraldmuvengei06/is-continuous-learning-helping-your-web-development-career-2bc5</guid>
      <description>&lt;h2&gt;
  
  
  My Big Question
&lt;/h2&gt;

&lt;p&gt;Hello folks, here's another one and the topic is "Is Continuous Learning Helping your Web Development Career?". This is a critical question that I've been asking myself every now and then. I'll tell you what I think. Hopefully this helps many other developers out there who're asking the same question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Facts
&lt;/h2&gt;

&lt;p&gt;Becoming a software developer is something that interests every beginner, every person who has just entered the field. It's interesting at first but it becomes serious when you're depending on writing code to pay bills. Even when you're really beat, you have to write code, cause in most cases its your main source of income. You can't just take a long break or can you? &lt;br&gt;
Again, when you're a web developer, its more challenging. There's a lot of frameworks and libraries popping up each now and then. You're learning a framework today, tomorrow there's something else. You're using React in your current job, when you move to another company, they're using Vue or even Angular. I know there's so much in common between all of these technologies, but again, how can you be a top notch professional when you're jumping between frameworks or libraries. &lt;br&gt;
In summary, there's so much to learn! To remain competitive in the software development field, you have to never stop learning. It's a continuous process.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Brief Journey
&lt;/h2&gt;

&lt;p&gt;Personally, I started as a PHP/Laravel Developer. I worked for a startup in my town where I developed over 5 web based applications - Fee management system, Tenant Management System and many more. After a few months, there came another guy who knew JQuery really well. He could do CRUD operations without refreshing the page. My former boss developed some interest in him. I was afraid of loosing my job. I had to stay competitive, I had to know SPAs. So I learned Vue js and now I would develop interactive applications with Vue and Laravel. It helped me remain really competitive. I didn't lose my job 😊! I ended up loving Vue js. Now I work as a Frontend developer with Vue.js thanks to learning Vue! That's just one instance. There have been many occasions through my Web Development Career where I've had to learn new stuff just to remain competitive. Most of which I have not used. As a matter of fact, there are times when I feel &lt;em&gt;spread thin&lt;/em&gt;! (Knowing too much stuff and not being a professional in each)  &lt;/p&gt;

&lt;h2&gt;
  
  
  My View
&lt;/h2&gt;

&lt;p&gt;As much as its advisable to keep learning new stuff, I prefer being a professional in one or two frameworks! Pick a language, like Javascript choose a framework, like React, or even Vue. Learn it! Develop solutions, be a Professional React or Vue developer. If anything, be a professional! &lt;/p&gt;

&lt;h2&gt;
  
  
  What's your view?
&lt;/h2&gt;

&lt;p&gt;So what's your view? What's your experience? Let me know in the comment section below.&lt;/p&gt;

&lt;p&gt;Cover photo by &lt;a href="https://unsplash.com/s/photos/learning-code" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>productivity</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to Use Environment Variables (env) in ExpressJs</title>
      <dc:creator>muvengei.dev</dc:creator>
      <pubDate>Thu, 21 Oct 2021 13:49:28 +0000</pubDate>
      <link>https://forem.com/geraldmuvengei06/how-use-environment-variables-env-in-expressjs-3bpc</link>
      <guid>https://forem.com/geraldmuvengei06/how-use-environment-variables-env-in-expressjs-3bpc</guid>
      <description>&lt;p&gt;Well, hello!&lt;br&gt;
If you're coming from frontend development with Vue, or React, you know that environment variables (.env) are initialized behind the scenes i.e. You don't have to require and use &lt;code&gt;dotenv&lt;/code&gt;. However, when writing backend with, Expressjs, for instance, you have to initialize it like so:-&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./.env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Problem: Env variable not found
&lt;/h2&gt;

&lt;p&gt;A friend was building an API with Expressjs. He needed to connect to stripe for the payments. The response from stripe was &lt;code&gt;Authorization headers are missing&lt;/code&gt;. He couldn't get why this was happening despite the fact that he had defined the Stripe Secret Key on the environment variables (.env), and used it when initializing "stripe" npm package. &lt;/p&gt;

&lt;h2&gt;
  
  
  Server.js
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;..&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stripeRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./routes/stripe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;..&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./.env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;..&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  routes/stripe.js
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stripe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stripe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRIPE_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  .env
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;STRIPE_KEY=key_goes_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Soln: Define environment variables before using them
&lt;/h1&gt;

&lt;p&gt;The error was as a result of defining the Stripe routes before requiring the &lt;code&gt;.env&lt;/code&gt;. This solved his issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./.env&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;stripeRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./routes/stripe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Personally I like to define my environment variables at the start of the server file. &lt;/p&gt;

</description>
      <category>node</category>
      <category>express</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Cool Projects to Help You Learn React Js - Part 2</title>
      <dc:creator>muvengei.dev</dc:creator>
      <pubDate>Thu, 07 Oct 2021 16:35:09 +0000</pubDate>
      <link>https://forem.com/geraldmuvengei06/cool-projects-to-help-you-learn-react-js-part-2-1f7a</link>
      <guid>https://forem.com/geraldmuvengei06/cool-projects-to-help-you-learn-react-js-part-2-1f7a</guid>
      <description>&lt;p&gt;Hello there 👋, citizens of the web!&lt;br&gt;
A couple of days ago I decided to write a series of articles on &lt;em&gt;Cool Projects to Help You Learn React Js&lt;/em&gt;. I remember when I started learning react, I went through the documentation, then I had to come up with interesting projects to help me grasp the concepts. I needed an &lt;a href="https://www.webopedia.com/definitions/api/" rel="noopener noreferrer"&gt;API&lt;/a&gt; that I'd consume. However I didn't have the time to start building one. I went through the web and found interesting APIs that are publicly available for anyone to use. This saved me much time to work on other projects. &lt;/p&gt;

&lt;p&gt;Now, I'd like to share the APIs with you so that you can bootstrap on React ASAP, and save time while doing it! After all, who doesn't need extra seconds? 😃 Note that you can consume these APIs with any frontend framework of your choice. Be it ReactJS, React Native, VueJS, Flutter, Angular, etc. &lt;/p&gt;

&lt;p&gt;For the ReactJS enthusiast, I'll point out ReactJS concepts you can learn through each of these APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's dive in!
&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%2F01spub2o74oxi6yj8010.gif" 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%2F01spub2o74oxi6yj8010.gif" alt="ToddlerDiveChildGIF" width="445" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;a href="https://api.nasa.gov/" rel="noopener noreferrer"&gt;NASA Open APIs&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Welcome to the NASA API portal. The objective of this site is to make NASA data, including imagery, eminently accessible to application developers. This catalog focuses on broadly useful and user friendly APIs and does not hold every NASA API. - Nasa&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Isn't this amazing? This API allows you to integrate these NASA functionalities into your build:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Astronomy Picture of the Day&lt;/li&gt;
&lt;li&gt;Space Weather Database Of Notifications, Knowledge, Information&lt;/li&gt;
&lt;li&gt;Earth Observation Data and Natural Event Triacker&lt;/li&gt;
&lt;li&gt;Mars Weather Service API - This could be useful in the event of a zombie apocalypse and we all move to Mars 😂&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And many more that you'll find on the official website.&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%2Fu9n17moc6g9wzt91cshp.gif" 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%2Fu9n17moc6g9wzt91cshp.gif" alt="MarsNeedsYouGIF" width="498" height="280"&gt;&lt;/a&gt;&lt;br&gt;
 ### What you'll learn while building this project&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React Router - In case you need multiple pages for your project&lt;/li&gt;
&lt;li&gt;Rendering Elements, Conditional Rendering&lt;/li&gt;
&lt;li&gt;State and Lifecycle&lt;/li&gt;
&lt;li&gt;Handling Events&lt;/li&gt;
&lt;li&gt;Lists and Keys, Forms&lt;/li&gt;
&lt;li&gt;Code Splitting, Context&lt;/li&gt;
&lt;li&gt;React Hooks -useEffect, useState, useParams, etc &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. &lt;a href="https://developers.coinbase.com" rel="noopener noreferrer"&gt;Coinbase Crypto API&lt;/a&gt;
&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%2F5x2i5cci47gpa0ic5ald.gif" 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%2F5x2i5cci47gpa0ic5ald.gif" alt="ChangeangelDidYouSayCryptoGIF" width="392" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Crypto nerds I got something for you! You can build your own crypto trading web app where you can:- &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check the price of a crypto&lt;/li&gt;
&lt;li&gt;Buy it&lt;/li&gt;
&lt;li&gt;Track your activity&lt;/li&gt;
&lt;li&gt;Get notifications when the price of a crypto changes and more
React Fetch API or Axios while getting or posting data to the api. &lt;/li&gt;
&lt;li&gt;You can also integrate charts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What You'll Learn
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Authentication - The Coinbase API provides a passport authentication capability.&lt;/li&gt;
&lt;li&gt;Protecting Routes&lt;/li&gt;
&lt;li&gt;useFetch, React Fetch API or Axios while getting or posting data to the api. &lt;/li&gt;
&lt;li&gt;You can also integrate charts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is more of a real world app that you can add to your portfolio. If you do it just right, it can help you secure a Frontend Job!&lt;/p&gt;

&lt;p&gt;Let me leave it at that! If you like this article you can leave a comment below. You can also share other open APIs we can use to learn stuff. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/geraldmuvengei06/cool-projects-to-help-you-learn-react-js-part-1-g19"&gt;You can also check out part 1 of this series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.fiverr.com/s/ca01sf" rel="noopener noreferrer"&gt;I can also help you with your Reactjs code&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>reactnative</category>
      <category>vue</category>
    </item>
    <item>
      <title>Cool Projects to Help You Learn React JS - Part 1</title>
      <dc:creator>muvengei.dev</dc:creator>
      <pubDate>Tue, 05 Oct 2021 21:18:15 +0000</pubDate>
      <link>https://forem.com/geraldmuvengei06/cool-projects-to-help-you-learn-react-js-part-1-g19</link>
      <guid>https://forem.com/geraldmuvengei06/cool-projects-to-help-you-learn-react-js-part-1-g19</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is React
&lt;/h3&gt;

&lt;p&gt;React is a declarative and component-based Javascript Library for building web interfaces. Its developed and maintained by  &lt;a href="https://facebook.com" rel="noopener noreferrer"&gt;Facebook&lt;/a&gt;. Although it's grouped with other "Frameworks" like VueJS and Angular, its termed much as a Library and not a framework. &lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;Find out more about React on this link&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this article?
&lt;/h3&gt;

&lt;p&gt;Personally, when I want to learn a new Framework, I go through the documentation to grasp the basic and core 'Hows and Whys', then I start basic applications to practice the concepts I've learned. This has always worked like magic for me. It goes without saying that learning by doing does a lot when it comes to grasping new concepts. I choose this approach since writing code is like a science, its a creative, intuitive and involving activity, like Math. Hence, learning by doing does it!&lt;/p&gt;

&lt;p&gt;Let's skip the poetry and head first into the interesting part of this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interesting React Projects
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. React Clock Faces A.K.A. ReactCF - A Simple React.Js Clock With Many Faces
&lt;/h3&gt;

&lt;p&gt;For this projects you'll learn these skills. The purpose of the project can be to display a Digital Clock that updates itself after every second, just like an actual digital watch. You can have different styles, or rather faces for displaying your digital clock. &lt;code&gt;Hint: Use Javascript's built in function: setTimeout()&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React Components&lt;/li&gt;
&lt;li&gt;Reusability&lt;/li&gt;
&lt;li&gt;React DOM and Reactivity&lt;/li&gt;
&lt;li&gt;React Hooks: useEffect, useState&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Stockfoto
&lt;/h3&gt;

&lt;p&gt;However you may call it, the main aim of this project is to consume Unsplash Photos API, which is open source and have fun while doing it. &lt;em&gt;Note: Read through &lt;a href="https://unsplash.com/documentation" rel="noopener noreferrer"&gt;Unsplash API Guildlines&lt;/a&gt; to make sure you're not breaking any rules.&lt;/em&gt; This will be a very interesting project!&lt;br&gt;
The api and interesting endpoints&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Random Picture&lt;/li&gt;
&lt;li&gt;Latest pictures&lt;/li&gt;
&lt;li&gt;Search Pictures&lt;/li&gt;
&lt;li&gt;Get One Picture and download picture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What you will learn:-&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React Components&lt;/li&gt;
&lt;li&gt;Reusability&lt;/li&gt;
&lt;li&gt;React DOM and Reactivity&lt;/li&gt;
&lt;li&gt;React Hooks: useEffect, useState&lt;/li&gt;
&lt;li&gt;React fetchAPI&lt;/li&gt;
&lt;li&gt;ES6&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cool Features you could implement.
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Lazy loading Images&lt;/li&gt;
&lt;li&gt;Lazy Loading Pagination - Load more images when a user scrolls to the bottom&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll be happy to see what you create!&lt;br&gt;
&lt;a href="http://www.fiverr.com/s/ca01sf" rel="noopener noreferrer"&gt;I can also help you with your Reactjs code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you wait for Part 2.&lt;br&gt;
Regards&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>unsplash</category>
    </item>
  </channel>
</rss>
