<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Teddy ASSIH</title>
    <description>The latest articles on DEV Community by Teddy ASSIH (@ion_finisher).</description>
    <link>https://dev.to/ion_finisher</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%2F2167626%2F8d994821-58e1-44ad-85a6-193c7fdb54bc.jpg</url>
      <title>DEV Community: Teddy ASSIH</title>
      <link>https://dev.to/ion_finisher</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ion_finisher"/>
    <language>en</language>
    <item>
      <title>MailPlanet🌍: Visualize email origins on a global map</title>
      <dc:creator>Teddy ASSIH</dc:creator>
      <pubDate>Sun, 08 Jun 2025 14:50:54 +0000</pubDate>
      <link>https://dev.to/ion_finisher/mailplanet-visualize-email-origins-on-a-global-map-33fo</link>
      <guid>https://dev.to/ion_finisher/mailplanet-visualize-email-origins-on-a-global-map-33fo</guid>
      <description>&lt;p&gt;This is a submission for the &lt;a href="https://dev.to/challenges/postmark"&gt;Postmark Challenge: Inbox Innovators&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Ever wondered &lt;em&gt;where on Earth&lt;/em&gt; your emails are really coming from? &lt;br&gt;
Well, Ethically, that's not really possible but we still can know where the email provider is sending that email from and now you can &lt;strong&gt;see&lt;/strong&gt; it — literally.&lt;/p&gt;

&lt;p&gt;That is what &lt;strong&gt;MailPlanet&lt;/strong&gt; is for, a Next.js-powered space station (okay, it's a web app, but let me dream) that pinpoints the origin of incoming emails and drops them on a glowing globe.&lt;/p&gt;

&lt;p&gt;It listens for emails using Postmark's inbound webhooks, geolocates the sender’s IP using Abstract API, stores the data with Drizzle ORM, and then — voilà — launches a little marker on the globe using Mapbox.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check it live here&lt;/strong&gt;: &lt;a href="https://mailplanet.vercel.app" rel="noopener noreferrer"&gt;https://mailplanet.vercel.app&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Preview Screenshot&lt;/strong&gt;:  &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%2F84wdjsdfl3q6xwkfu22l.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%2F84wdjsdfl3q6xwkfu22l.png" alt="MailPlanet Landing page"&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%2Fszjmx8ip62xtks1k3iy6.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%2Fszjmx8ip62xtks1k3iy6.png" alt="MailPlanet Interactive map"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Testing Instructions
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Visit the live link above.&lt;/li&gt;
&lt;li&gt;Send an email to this Postmark inbound address (&lt;code&gt;2446e801971b017b07291e89d25592b3@inbound.postmarkapp.com&lt;/code&gt;) — don’t worry, any subject and body will do.&lt;/li&gt;
&lt;li&gt;Wait a few seconds for MailPlanet to pick it up, geo-locate it, and drop a marker from space.&lt;/li&gt;
&lt;li&gt;Click the marker on the globe to see who emailed and from where&lt;/li&gt;
&lt;/ol&gt;

 
&lt;h2&gt;
  
  
  Code Repository
&lt;/h2&gt;

&lt;p&gt;Code’s open source and ready for launch:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Ionfinisher" rel="noopener noreferrer"&gt;
        Ionfinisher
      &lt;/a&gt; / &lt;a href="https://github.com/Ionfinisher/mail-planet" rel="noopener noreferrer"&gt;
        mail-planet
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;MailPlanet 🌍📧&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;MailPlanet is a Next.js application that visualizes the geographic origin of incoming emails on an interactive 3D globe. It processes inbound emails via Postmark webhooks, performs IP geolocation, and displays the data on a Mapbox map.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Ionfinisher/mail-planet/public/mailplanet-screenshot.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FIonfinisher%2Fmail-planet%2Fpublic%2Fmailplanet-screenshot.png" alt="MailPlanet Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interactive 3D Globe&lt;/strong&gt;: Visualizes email origins using Mapbox GL JS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time Updates&lt;/strong&gt;: New emails appear on the map as they are processed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP Geolocation&lt;/strong&gt;: Determines the location of the email sender's IP address (actually the email provider's address).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email Data Display&lt;/strong&gt;: Shows details like sender, subject, and location on map markers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database Integration&lt;/strong&gt;: Stores and retrieves IP location data using Drizzle ORM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Postmark Webhook&lt;/strong&gt;: Processes inbound emails received through Postmark.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tech Stack&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework&lt;/strong&gt;: &lt;a href="https://nextjs.org/" rel="nofollow noopener noreferrer"&gt;Next.js&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language&lt;/strong&gt;: &lt;a href="https://www.typescriptlang.org/" rel="nofollow noopener noreferrer"&gt;TypeScript&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mapping&lt;/strong&gt;: &lt;a href="https://docs.mapbox.com/mapbox-gl-js/api/" rel="nofollow noopener noreferrer"&gt;Mapbox GL JS&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database ORM&lt;/strong&gt;: &lt;a href="https://orm.drizzle.team/" rel="nofollow noopener noreferrer"&gt;Drizzle ORM&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling&lt;/strong&gt;: &lt;a href="https://tailwindcss.com/" rel="nofollow noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt; (Assumed, common with Next.js)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inbound Email&lt;/strong&gt;: &lt;a href="https://postmarkapp.com/" rel="nofollow noopener noreferrer"&gt;Postmark&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geolocation API&lt;/strong&gt;: &lt;a href="https://www.abstractapi.com/ip-geolocation-api" rel="nofollow noopener noreferrer"&gt;Abstract API&lt;/a&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Ionfinisher/mail-planet" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Here’s how it all came together:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Email Receiving – Powered by Postmark
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Set up an inbound stream and webhook that sends all incoming emails to my Next.js API route &lt;code&gt;/api/inbound-email&lt;/code&gt;. (Can only receive up to 99-ish emails so be kind 😁)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Grabbed the sender's mail service IP from the email headers.&lt;/li&gt;
&lt;li&gt;Fed that IP into the &lt;a href="https://www.abstractapi.com/ip-geolocation-api" rel="noopener noreferrer"&gt;Abstract IP Geolocation API&lt;/a&gt; to get the country, lat/lng, and flag.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Data Persistence
&lt;/h3&gt;

&lt;p&gt;The decision to actually use a database came into play as I'm using free tier only services and I had to gatekeep all the API calls.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Used &lt;strong&gt;Drizzle ORM&lt;/strong&gt; to define the schema and store everything: IPs, locations, email counts, etc.&lt;/li&gt;
&lt;li&gt;If an IP shows up again, it just increments the count like a loyal globe-tracker.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Globe Rendering
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Built a slick Mapbox globe via [&lt;code&gt;src/components/Planet.tsx&lt;/code&gt;].&lt;/li&gt;
&lt;li&gt;Pulled geolocation data from &lt;code&gt;/api/iplocations&lt;/code&gt; and mapped every email sender in real-time.&lt;/li&gt;
&lt;li&gt;Clicking a marker shows details like subject and sender.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;&lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Emails&lt;/td&gt;
&lt;td&gt;&lt;a href="https://postmarkapp.com/" rel="noopener noreferrer"&gt;Postmark&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Geolocation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.abstractapi.com/ip-geolocation-api" rel="noopener noreferrer"&gt;Abstract API&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;&lt;a href="https://orm.drizzle.team/" rel="noopener noreferrer"&gt;Drizzle ORM&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maps&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.mapbox.com/mapbox-gl-js/api/" rel="noopener noreferrer"&gt;Mapbox GL JS&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Experience with Postmark
&lt;/h2&gt;

&lt;p&gt;MailPlanet was built with one goal: use the Postmark inbound  email feture to make emails &lt;em&gt;less boring&lt;/em&gt; with a sprinkle of creativity. And Postmark made it really simple to do so, from getting my account verified, to configuring the inbound mail server in just a few steps.&lt;/p&gt;

&lt;p&gt;Thanks Postmark for the stellar tools. ✨&lt;br&gt;&lt;br&gt;
Now go forth and send an email — MailPlanet is watching. 🌎&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>postmarkchallenge</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>Bot Blitz: Host Your HTML5 Game on Alibaba Cloud OSS</title>
      <dc:creator>Teddy ASSIH</dc:creator>
      <pubDate>Mon, 28 Apr 2025 04:06:59 +0000</pubDate>
      <link>https://dev.to/ion_finisher/bot-blitz-host-your-html5-game-on-alibaba-cloud-oss-245g</link>
      <guid>https://dev.to/ion_finisher/bot-blitz-host-your-html5-game-on-alibaba-cloud-oss-245g</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://int.alibabacloud.com/m/1000402443/" rel="noopener noreferrer"&gt;Alibaba Cloud&lt;/a&gt; Challenge: &lt;a href="https://dev.to/challenges/alibaba"&gt;Build a Web Game&lt;/a&gt;.&lt;/em&gt;*&lt;/p&gt;

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

&lt;p&gt;I created &lt;strong&gt;Bot Blitz&lt;/strong&gt;, a fast-paced, browser-based and electrifying match-3 game where you swap colorful robot parts against the clock built with Phaser 3 and served entirely from OSS.&lt;/p&gt;

&lt;p&gt;The game leverages HTML5, CSS3, and JavaScript and Canvas for broad device support and light weight game developement.&lt;br&gt;
By hosting assets on OSS static website hosting, I eliminated the need for any backend servers, achieving a truly serverless deployment that’s easy to maintain and cost-efficient.&lt;/p&gt;
&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;



&lt;p&gt;Play &lt;strong&gt;Bot Blitz&lt;/strong&gt; live at:&lt;br&gt;
👉 &lt;a href="http://teddydev.tech" rel="noopener noreferrer"&gt;http://teddydev.tech&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 GitHub Repo here:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Ionfinisher" rel="noopener noreferrer"&gt;
        Ionfinisher
      &lt;/a&gt; / &lt;a href="https://github.com/Ionfinisher/bot-blitz" rel="noopener noreferrer"&gt;
        bot-blitz
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Bot Blitz 🤖💥&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Welcome to &lt;strong&gt;Bot Blitz&lt;/strong&gt;, the electrifying match-3 game where you swap colorful robot parts against the clock! ⏱️&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Ionfinisher/bot-blitz/assets/background.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FIonfinisher%2Fbot-blitz%2Fassets%2Fbackground.png" alt="Bot Blitz Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Get ready to match and blast your way through a grid of quirky robots. You've got just &lt;strong&gt;two minutes&lt;/strong&gt; to rack up the highest score possible. Can you beat the clock and become the ultimate Bot Blitzer?&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📸 Screenshots&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Desktop View Screenshot&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Ionfinisher/bot-blitz/assets/web-screenshot.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FIonfinisher%2Fbot-blitz%2Fassets%2Fweb-screenshot.png" alt="Desktop View  Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Mobile View Screenshot&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/Ionfinisher/bot-blitz/assets/mobile-screenshot.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FIonfinisher%2Fbot-blitz%2Fassets%2Fmobile-screenshot.jpg" alt="Mobile view Screenshot" width="300"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Features&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Classic Match-3 Fun:&lt;/strong&gt; Swap adjacent bots to create lines of 3 or more identical bots.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robotic Mayhem:&lt;/strong&gt; Match colorful robot parts in a vibrant grid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Beat the Clock:&lt;/strong&gt; Race against a 120-second timer to maximize your score! ⏳&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score &amp;amp; Combo Tracking:&lt;/strong&gt; Watch your score climb and aim for the highest combo chain! ✨&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent Leaderboard:&lt;/strong&gt; You can see your Top 10 highest scores! 🏆 (Uses browser &lt;code&gt;localStorage&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sound Effects &amp;amp; Music:&lt;/strong&gt; Immersive sounds for swaps, matches, combos, and background…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Ionfinisher/bot-blitz" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Here are a few snapshots of the action:&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%2Fwkzxfafa3g2oq1xd02xk.jpg" 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%2Fwkzxfafa3g2oq1xd02xk.jpg" alt="Mobile View Screenshot"&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%2Filq7ih0t8zojldxc5rk6.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%2Filq7ih0t8zojldxc5rk6.png" alt="Desktop view Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Alibaba Cloud Services Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Object Storage Service (OSS)
&lt;/h3&gt;

&lt;p&gt;Why Choose OSS for Your Static Game?&lt;/p&gt;

&lt;p&gt;Alibaba Cloud OSS gives you massively scalable, low-cost storage for static assets—perfect for games made entirely in HTML/CSS/JS like Phaser. You get global availability, built-in high durability, and a pay-for-what-you-use model that can be free if you stay within the free tier limits. Since OSS handles HTTP GETs directly, there’s no backend maintenance, letting you focus on coding loops instead of servers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 1: Create and Configure Your Bucket&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Log in to the Alibaba Cloud Console and go to Object Storage Service (OSS).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click Create Bucket, choose a globally unique name, select your region, and pick Standard storage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set the bucket’s ACL to public-read so anyone can fetch your game files—later we’ll tighten this up with bucket policies.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Enable Server-Side Encryption if you want to protect your assets at rest.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Step 2: Enable Static Website Hosting&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;In the OSS console, navigate to Data Management → Static Page.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set your Default Homepage to index.html and Default 404 Page to 404.html (so players get a friendly “Oops!” if they type a wrong URL).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Save the settings—OSS will now treat your bucket like a web server and serve those files automatically.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Step 3: Upload Your Phaser Game Assets
You have two main ways to get your game into OSS:&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Console Upload: Drag-and-drop your index.html, 404.html, /assets, /css, /js folders directly in the OSS web panel.&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%2F871hnj879c9csymgdh3i.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%2F871hnj879c9csymgdh3i.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ossutil CLI:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Download and configure ossutil with your AccessKey ID/Secret.&lt;/p&gt;

&lt;p&gt;From your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ossutil &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ./public oss://your-bucket-name/ &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This recursively uploads your local public/ folder to OSS &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 4: Point a Custom Domain &amp;amp; Enable HTTPS
Map your domain via CNAME to your-bucket-name.endpoint.aliyuncs.com in your DNS provider.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;In the OSS console, go to Domain Names → Map Custom Domain, verify ownership with a TXT record, then add the CNAME.&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%2Ff7m22nc1ewwz24788muj.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%2Ff7m22nc1ewwz24788muj.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload your SSL certificate (public .crt and private .key) under Certificate Management to serve your game over HTTPS.
I couldn't actually achieve this step yet cause of some providers issues.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Step 5: Speed It Up with CDN, Caching
To reduce latency and make your sprites glide faster:&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Enable Alibaba Cloud CDN on your bucket for edge caching worldwide.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set Cache-Control headers (e.g., max-age=31536000, immutable) on versioned assets so browsers cache indefinitely.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Game Development Highlights
&lt;/h2&gt;

&lt;p&gt;Built with Phaser 3: Leveraging the power of the fun, free, and fast Phaser game framework. And it;s just pure HTML5, CSS and JS making it lightweight 🍃.&lt;/p&gt;

&lt;p&gt;Simple and easy gameplay: Classic Match-3 Fun gameplay but yet so addictive and stress relieving. Just swap adjacent bots to create lines of 3 or more identical bots.&lt;/p&gt;

&lt;p&gt;Responsive Design: Playable on different screen sizes.&lt;/p&gt;

&lt;p&gt;Sound Effects &amp;amp; Music: Immersive sounds for swaps, matches, combos, and background music (with mute options!)&lt;/p&gt;

</description>
      <category>alibabachallenge</category>
      <category>devchallenge</category>
      <category>gamedev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Pulumi ESC Config: Simplify Your Local Configuration Management</title>
      <dc:creator>Teddy ASSIH</dc:creator>
      <pubDate>Sun, 06 Apr 2025 19:01:55 +0000</pubDate>
      <link>https://dev.to/ion_finisher/pulumi-esc-config-simplify-your-local-configuration-management-40jk</link>
      <guid>https://dev.to/ion_finisher/pulumi-esc-config-simplify-your-local-configuration-management-40jk</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/pulumi"&gt;Pulumi Deploy and Document Challenge&lt;/a&gt;: Shhh, It's a Secret!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fimages.unsplash.com%2Fphoto-1607798748738-b15c40d33d57%3Fq%3D80%26w%3D2070%26auto%3Dformat%26fit%3Dcrop" 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%2Fimages.unsplash.com%2Fphoto-1607798748738-b15c40d33d57%3Fq%3D80%26w%3D2070%26auto%3Dformat%26fit%3Dcrop" alt="Cover image: Configuration management dashboard" width="2070" height="1380"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;I created &lt;strong&gt;Pulumi ESC Config&lt;/strong&gt;, a command-line tool that bridges the gap between Pulumi's Environment Secrets and Configuration (ESC) service and application configuration files. This tool securely fetches secrets from Pulumi ESC and transforms them into various configuration formats (.env, JSON, YAML) that any application can easily consume.&lt;/p&gt;

&lt;p&gt;The tool addresses a common challenge in modern development: securely managing secrets across different environments while maintaining a consistent interface for applications to access them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live Demo Link
&lt;/h2&gt;

&lt;p&gt;This project doesn't have a live Link.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Repo
&lt;/h2&gt;

&lt;p&gt;The project is available on GitHub: &lt;a href="https://github.com/Ionfinisher/pulumi-esc-config" rel="noopener noreferrer"&gt;pulumi-esc-config&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secure fetching of secrets from Pulumi ESC&lt;/li&gt;
&lt;li&gt;Support for multiple output formats (.env, JSON, YAML)&lt;/li&gt;
&lt;li&gt;Environment-specific configuration generation&lt;/li&gt;
&lt;li&gt;Simple CLI interface with comprehensive documentation&lt;/li&gt;
&lt;/ul&gt;

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

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

&lt;p&gt;As a developer working across multiple projects and environments, we often found ourselve constantly copying secrets between different configuration files and worrying about accidentally committing sensitive information to version control. We therefore need a solution that would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep secrets secure and centralized&lt;/li&gt;
&lt;li&gt;Support multiple application types (Node.js, Python, etc.)&lt;/li&gt;
&lt;li&gt;Make environment-specific configuration easy to manage&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;Pulumi ESC offered the perfect foundation for secure secret management, but I needed a way to seamlessly integrate it with different application frameworks. That's when I decided to build the ESC Config Generator.&lt;/p&gt;

&lt;h3&gt;
  
  
  Development Process
&lt;/h3&gt;

&lt;p&gt;I started by exploring the Pulumi ESC SDK and understanding how to authenticate and retrieve secrets. The JavaScript SDK made this relatively straightforward:&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;esc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DefaultClient&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;openEnv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;openAndReadEnvironment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orgName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;projName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;envName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I built the formatting layer to transform the retrieved secrets into different configuration file formats. This involved creating custom serializers for .env, JSON, and YAML formats:&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="c1"&gt;// For .env files&lt;/span&gt;
&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;)&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;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;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="nx"&gt;value&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// For YAML files&lt;/span&gt;
&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// For JSON files&lt;/span&gt;
&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&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="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most challenging part was handling the different ways applications consume configuration files. I researched various frameworks and added examples to the documentation showing how to load the generated configurations in Node.js, Python.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned
&lt;/h3&gt;

&lt;p&gt;Through this project, I gained valuable insights into:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Secret Management Best Practices&lt;/strong&gt;: The importance of secret rotation, least privilege access, and environment isolation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDK Integration&lt;/strong&gt;: How to work with third-party SDKs and handle authentication flows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI Design&lt;/strong&gt;: Creating an intuitive command-line interface with clear documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-Language Support&lt;/strong&gt;: Supporting multiple programming languages and frameworks&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Using Pulumi ESC
&lt;/h2&gt;

&lt;p&gt;Pulumi ESC provided the ideal foundation for this project for several reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Centralized Secret Management&lt;/strong&gt;: ESC offers a secure, centralized place to store secrets with proper access controls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment Segmentation&lt;/strong&gt;: The project/environment structure aligns perfectly with development workflows (dev/staging/prod)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDK Availability&lt;/strong&gt;: The JavaScript SDK made integration straightforward&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong Security Model&lt;/strong&gt;: Pulumi's security practices gave me confidence in building on their platform&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tool interacts with Pulumi ESC through these key steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Authentication via the ESC CLI: &lt;code&gt;esc login&lt;/code&gt;. Actually this was the easiest and secured way of authentication I found to integrate in this project&lt;/li&gt;
&lt;li&gt;Defining the organization, project, and environment context&lt;/li&gt;
&lt;li&gt;Retrieving secrets using the SDK: &lt;code&gt;client.openAndReadEnvironment()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Transforming and saving the secrets to the appropriate format&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's a practical example of generating a .env file for a development environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run generate &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--proj&lt;/span&gt; myproject &lt;span class="nt"&gt;--env&lt;/span&gt; dev &lt;span class="nt"&gt;--type&lt;/span&gt; &lt;span class="nb"&gt;env&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benefits Over Alternative Approaches
&lt;/h3&gt;

&lt;p&gt;Compared to other secret management approaches, using Pulumi ESC provided:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Better Security&lt;/strong&gt;: Secrets never need to be manually copied or stored in insecure locations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified Workflows&lt;/strong&gt;: Developers can focus on code instead of configuration management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent Experience&lt;/strong&gt;: The same approach works across all environments and languages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit Capabilities&lt;/strong&gt;: All secret access is logged and auditable&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;I'm planning to enhance the tool with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Secret rotation reminders and automation&lt;/li&gt;
&lt;li&gt;Integration of OIDC generation for differents providers&lt;/li&gt;
&lt;li&gt;Support for more configuration formats&lt;/li&gt;
&lt;li&gt;A web interface for non-technical users&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're struggling with configuration management across environments, give Pulumi ESC Config Generator a try!&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>pulumichallenge</category>
      <category>webdev</category>
      <category>cloud</category>
    </item>
    <item>
      <title>IntelliGrid: AI KendoReact Pricing Grid generator 🤖</title>
      <dc:creator>Teddy ASSIH</dc:creator>
      <pubDate>Sun, 23 Mar 2025 06:01:45 +0000</pubDate>
      <link>https://dev.to/ion_finisher/intelligrid-ai-kendoreact-pricing-grid-generator-1jn0</link>
      <guid>https://dev.to/ion_finisher/intelligrid-ai-kendoreact-pricing-grid-generator-1jn0</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/kendoreact"&gt;KendoReact Free Components Challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Recently, I was building a pricing grid section for a landing page and I'd run out of ideas for prices but also for modules, and of course I wondered if there wasn't an AI tool to speed things up. Then I came up with the idea of simply building it (builder mentality 🛠💻). There was also thid Dev challenge so I tought if I could mix both, it'll be cool. So here we are with a AI tool that can generate Pricing Grids made with Kendo React Components based on the submitted form✨.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo
&lt;/h3&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%2Fc83c3xndk9b2400bxd55.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%2Fc83c3xndk9b2400bxd55.png" alt="Configuration Form" width="800" height="401"&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%2Fcynvzkoh37c447c34dte.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%2Fcynvzkoh37c447c34dte.png" alt="Generating" width="800" height="401"&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%2Fnmw2iajwria3wmerabq8.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%2Fnmw2iajwria3wmerabq8.png" alt="Pricing grid" width="800" height="401"&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%2Fi8oyevsdnx6y2bdwpzw6.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%2Fi8oyevsdnx6y2bdwpzw6.png" alt="Generated Code" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check It Live &lt;a href="https://intelligrid-alpha.vercel.app" rel="noopener noreferrer"&gt;HERE&lt;/a&gt;&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check the Repo &lt;a href="https://github.com/Ionfinisher/intelligrid" rel="noopener noreferrer"&gt;HERE&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  KendoReact Experience
&lt;/h2&gt;

&lt;p&gt;I leveraged KendoReact Free Components to build a user-friendly, responsive interface for my AI Pricing Grid generator app. Specifically, I used the following components:  &lt;/p&gt;

&lt;p&gt;From UI element packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Button from "@progress/kendo-react-buttons"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Input, TextArea, Slider, SliderLabel from "@progress/kendo-react-inputs"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Error, Label from "@progress/kendo-react-labels"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DropDownList from "@progress/kendo-react-dropdowns"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TabStrip, TabStripTab, Card, CardHeader, CardTitle, CardSubtitle, CardBody, CardFooter from "@progress/kendo-react-layout"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SvgIcon from "@progress/kendo-react-common"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Badge, Loader from "@progress/kendo-react-indicators"&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Icons from KendoReact:&lt;/p&gt;

&lt;p&gt;downloadIcon, codeIcon, eyeIcon, checkIcon from "@progress/kendo-svg-icons"&lt;/p&gt;

&lt;p&gt;KendoReact’s components allowed me to customize the look and feel to match my app’s branding while maintaining performance and accessibility. The free tier’s robust features allowed me to prototype quickly and simplified AI integration and is easy to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  AIm to Impress
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;User enters product details via KendoReact form components&lt;/li&gt;
&lt;li&gt;Form data is sent to the server-side function&lt;/li&gt;
&lt;li&gt;The AI generates structured JSON data for pricing tiers&lt;/li&gt;
&lt;li&gt;Response is validated and processed&lt;/li&gt;
&lt;li&gt;UI displays the generated pricing grid&lt;/li&gt;
&lt;li&gt;User can download React code using KendoReact components&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>devchallenge</category>
      <category>kendoreactchallenge</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>KeyMash, type your ways to feature with DevCycle feature flags</title>
      <dc:creator>Teddy ASSIH</dc:creator>
      <pubDate>Mon, 23 Dec 2024 07:39:13 +0000</pubDate>
      <link>https://dev.to/ion_finisher/keymash-type-your-ways-to-feature-with-devcycle-feature-flags-33a0</link>
      <guid>https://dev.to/ion_finisher/keymash-type-your-ways-to-feature-with-devcycle-feature-flags-33a0</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/devcycle"&gt;DevCycle Feature Flag Challenge&lt;/a&gt;: Feature Flag Funhouse&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Hey everybody, Teddy here 👋. The listtle story behing my project is that I'm not that good at typing, and lately, I've been exercising my typing skills. So, I thought, "Why not turn my actual struggle into a fun project for this hackaton?" That’s how KeyMash came to life.&lt;/p&gt;

&lt;p&gt;It’s a super fun, game-like typing platform where you can put your skills to the test, challenge yourself (and maybe others down the line), and customize your experience with awesome feature flags as you finish tests. No need to create an account to jump in—it’s all about that instant, arcade vibe!&lt;/p&gt;

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

&lt;p&gt;Test it live here &lt;a href="https://keymash.vercel.app" rel="noopener noreferrer"&gt;KeyMash&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Screenshots:&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%2Fqyshpreov17ht2cjiflm.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%2Fqyshpreov17ht2cjiflm.png" alt="Homepage" width="800" height="396"&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%2Fmv9fq7etbmseinsdh8mt.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%2Fmv9fq7etbmseinsdh8mt.png" alt="Form" width="800" height="396"&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%2Fl1et9k5nqo6jsp9wuq28.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%2Fl1et9k5nqo6jsp9wuq28.png" alt="Typing-test" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My Code
&lt;/h2&gt;

&lt;p&gt;GitHub repo: &lt;a href="https://github.com/Ionfinisher/keymash" rel="noopener noreferrer"&gt;https://github.com/Ionfinisher/keymash&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  My DevCycle Experience
&lt;/h2&gt;

&lt;p&gt;Using DevCycle was really a fun coding time! Using feature flags globally was a breeze since I didn't need flags for individual users. It made everything more straightforward and organized. The React SDK was very easy to set up, and switching features on and off felt like flipping light switches and enjoyable!&lt;/p&gt;

&lt;p&gt;For instance, I used feature flags to introduce some UX and UI features, and even possible arcade extras in the future. It also reassured me that I could launch features gradually or test them safely.&lt;/p&gt;

&lt;p&gt;In summary, using DevCycle was a smooth experience for me in creating a strong base for a game that can continue to evolve with new ideas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Prize Categories
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;OpenFeature Aficionado: Awarded to a project that showcases the most creative use of a DevCycle OpenFeature Provider!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API All-Star: Awarded to a project that shines by making full use of the DevCycle API— showcase creativity and capability through effective integration!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>devcyclechallenge</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>MedInsight: Your AI rare disease research assistant.</title>
      <dc:creator>Teddy ASSIH</dc:creator>
      <pubDate>Sun, 10 Nov 2024 00:16:06 +0000</pubDate>
      <link>https://dev.to/ion_finisher/medinsight-your-ai-rare-disease-research-assistant-5hjj</link>
      <guid>https://dev.to/ion_finisher/medinsight-your-ai-rare-disease-research-assistant-5hjj</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/pgai"&gt;Open Source AI Challenge with pgai and Ollama &lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;MedInsight is an AI-powered knowledge management tool tailored for med students, doctors and researchers studying rare diseases. This application consolidates research articles into one highly searchable database allowing medical professionals to quickly retrieve relevant information about rare diseases. At the moment, the data is about Acute Lymphoblastic Leukemia.&lt;/p&gt;

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

&lt;p&gt;Live link here &lt;a href="https://medinsight-lyart.vercel.app/" rel="noopener noreferrer"&gt;MedInsight&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repo here: &lt;a href="https://github.com/Ionfinisher/med-insight" rel="noopener noreferrer"&gt;MedInsight Repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;DISCLAIMER: The requests are very slow (15-20 seconds) due to all the RAG logic relying on the database and the timescale server not having enough ressources so my apologies🙏.&lt;/p&gt;

&lt;p&gt;The home page&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%2Fnvipb9yu6uljvj69e5r8.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%2Fnvipb9yu6uljvj69e5r8.png" alt="Homepage" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First question:&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%2Fv2uxmu3mnj3rvlxgtwx2.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%2Fv2uxmu3mnj3rvlxgtwx2.png" alt="first question" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Second question:&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%2Foj1xam4qd4nh0jpztolr.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%2Foj1xam4qd4nh0jpztolr.png" alt="second-question" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools Used
&lt;/h2&gt;

&lt;h4&gt;
  
  
  The architecture:
&lt;/h4&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%2F4kwzl4tovqn6j34k70hi.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%2F4kwzl4tovqn6j34k70hi.png" alt="MedInsight-architecture" width="800" height="653"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Timescale:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I used a pretty simple schema that works well for storing, searching, and embedding research papers in a structured way for retrieval-augmented generation (RAG) and similarity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;research_papers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;GENERATED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;IDENTITY&lt;/span&gt;
&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Syncing and creating automatically LLM embeddings for the research papers:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;pgvector&lt;/code&gt; and &lt;code&gt;pgvectorscale&lt;/code&gt; extensions allowed me to store vector embeddings in my database. The pgai Vectorizer builds on top of these extensions to automatically create and synchronize embeddings for any text data. Also, I made use of the vectorscale index automatically created after 100,000 rows of vector data are present using the &lt;code&gt;StreamingDiskANN&lt;/code&gt; index on Timescale Cloud for a blazing fast search experience⚡️.&lt;/p&gt;

&lt;p&gt;With one line of code, I defined a vectorizer that creates embeddings for the research papers in a table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_vectorizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; 
    &lt;span class="s1"&gt;'public.research_papers'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embedding_openai&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'text-embedding-3-small'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1536&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key_name&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'OPENAI_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;chunking&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chunking_recursive_character_text_splitter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;formatting&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;formatting_python_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'title: $title url: $url content: $chunk'&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;ul&gt;
&lt;li&gt;The RAG:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To implement the RAG, pgai LLM functions come in handy. I used them to implement the RAG system directly in my database by creating a function and calling it in my queries. &lt;code&gt;openai&lt;/code&gt; to embed the search and &lt;code&gt;llama3&lt;/code&gt; from a self hosted &lt;code&gt;Ollama&lt;/code&gt; instance from &lt;a href="//koyeb.com"&gt;Koyeb&lt;/a&gt; to generate a summarized answer.&lt;/p&gt;

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

&lt;p&gt;Overall I was fascinated about how easy it was to setup the vector database and the automatic embedding. It for sure saves a lot of time in the implementation of ai based apps. Even if I had some small bugs throughout the coding journey, I managed to ship the app within a day so the timescale experience is pretty straight up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improvement
&lt;/h3&gt;

&lt;p&gt;Of course my app is not perfect and there is a lot to improve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Query optimization to imrpove the speed of the requests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A chat history to not loose conversations&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any other improvement idea is welcome in the comments 🙏.&lt;/p&gt;

&lt;p&gt;Prize categories:&lt;/p&gt;

&lt;p&gt;Open-source Models from Ollama, Vectorizer Vibe, All the Extensions&lt;/p&gt;

&lt;p&gt;Image from &lt;a href="https://www.vecteezy.com/" rel="noopener noreferrer"&gt;vecteezy.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>pgaichallenge</category>
      <category>database</category>
      <category>ai</category>
    </item>
    <item>
      <title>Hacktober 2024: One of the best October of my dev adventure</title>
      <dc:creator>Teddy ASSIH</dc:creator>
      <pubDate>Tue, 29 Oct 2024 12:02:29 +0000</pubDate>
      <link>https://dev.to/ion_finisher/hacktober-2024-one-of-the-best-october-of-my-dev-adventure-534p</link>
      <guid>https://dev.to/ion_finisher/hacktober-2024-one-of-the-best-october-of-my-dev-adventure-534p</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest"&gt;2024 Hacktoberfest Writing challenge&lt;/a&gt;: Contributor Experience&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's the corrected paragraph:&lt;/p&gt;

&lt;p&gt;Ah, Hacktober – the month when the air smells like pumpkin spice, open-source contributions, and birthday parties because, yes, I was born in October :). This time, though, I found something special: a Hackathon that gamified open-source contributions, &lt;strong&gt;&lt;a href="//oss.gg"&gt;oss.gg&lt;/a&gt;&lt;/strong&gt;, and also had crazy prizes. It was perfect because I wanted a new PC, and one of the prizes was an M3 MacBook. Just like that, I found myself a birthday gift 😂.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it started
&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%2Fpvbgqhul8819yu42kclt.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%2Fpvbgqhul8819yu42kclt.png" alt="teddy-assih-hacktoberfest" width="800" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It all started pretty casually. I signed up for the Hacktober Hackathon with simple goals: submit a few PRs, open some issues, and make sure I had at least 4 PRs merged to collect my badges. So, in late September - early October, I started searching for &lt;code&gt;goodfirst-issue&lt;/code&gt; PRs to get started, and then I found the &lt;a href="//formbricks.com"&gt;formbricks.com&lt;/a&gt; repo, where they were talking about oss.gg. Just seeing the prizes was enough to get me to fork their repo. And that’s when it took off 🚀.&lt;/p&gt;

&lt;h2&gt;
  
  
  The projects I worked on
&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%2Fbadxdsli8nkricd6z0j0.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%2Fbadxdsli8nkricd6z0j0.png" alt="teddy-assih-submissions" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the beginning, I wanted to maximize my chances of getting those 4 PRs merged, so I started with quick fixes in smaller projects. But then it hit me: if I was serious about that MacBook, I'd need to focus my efforts. By the end, I had tons of PRs merged, but I'll just highlight the ones that truly mattered to me.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/shhossain/computer_science/pull/778" rel="noopener noreferrer"&gt;Computer science&lt;/a&gt;&lt;/strong&gt;: The first PR that got accepted was a doc translation. This one was pretty big since the entire repo is dedicated to explaining computer science concepts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/DhanushNehru/year-in-progress/pull/13" rel="noopener noreferrer"&gt;Year in progress&lt;/a&gt;&lt;/strong&gt;: In another contribution, I added features to a small project that provides an API for generating a progress bar of the current year (or for a custom date range).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/unkeyed/unkey/pull/2166" rel="noopener noreferrer"&gt;Unkey&lt;/a&gt;&lt;/strong&gt;: Another doc PR that was needed for the &lt;code&gt;@unkey/nextjs&lt;/code&gt; npm package page.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/unkeyed/unkey/pull/2272" rel="noopener noreferrer"&gt;Another Unkey PR&lt;/a&gt;&lt;/strong&gt;: Then came a request to create a template using one of their tools that’s showcased on their website. I made this 👉 &lt;a href="https://www.unkey.com/templates/python-django" rel="noopener noreferrer"&gt;Link to my template&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The fun part 🎉
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;oss.gg side quests&lt;/strong&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%2Fwvo1nkgcwu4m8ifbbt6j.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%2Fwvo1nkgcwu4m8ifbbt6j.png" alt=" " width="800" height="686"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The special touch of the oss.gg Hackathon was their side quests. You could submit no-code PRs, engage with the community on X and LinkedIn, and really get to know the projects. These side quests actually turned me into a more consistent technical writer and introduced me to some amazing, talented people along the way ✨.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code PRs Became a Daily Habit&lt;/strong&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%2F3q5sqeqqnv8tky2e4atb.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%2F3q5sqeqqnv8tky2e4atb.png" alt=" " width="800" height="229"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can confidently say that this month, I opened at least one PR, issue, or pushed some code &lt;em&gt;every single day&lt;/em&gt; – and that was just wild for me! Well, except for that one tiny day when things went way too fast to keep up. My mornings turned into “What can I ship today?” moments 😂.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Side Projects&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I started building side projects – mini-apps that used the functionality of the projects I was working on, mostly inspired by the oss.gg side quests. This sparked a real ship fast mentality in me, and now I just want more projects to build 😂. Here are two of the ones I made if you’d like to check them out: &lt;a href="https://askiq.vercel.app/" rel="noopener noreferrer"&gt;ASKIQ&lt;/a&gt; and &lt;a href="https://next-todont.vercel.app/" rel="noopener noreferrer"&gt;To don't App&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up:
&lt;/h2&gt;

&lt;p&gt;In the end, what started as a plan to submit “a few PRs” turned into one of my best months in open source. I learned, built, shipped, wrote, and connected in ways I didn’t think were possible in just a few weeks. Hacktober had made open source not just accessible, but addictive and rewarding. And now, here I am, with a refreshed GitHub and a ton of articles.&lt;br&gt;
What’s the takeaway? Sign up for a Hackathon, contribute in any way you can, and you just might discover you’re capable of more than you thought – and probably meet some epic people along the way ⭐️.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hacktoberfest</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Building an FAQ Generator API with Next.js, GPT-4, and Unkey: Making Rate Limiting Fun!</title>
      <dc:creator>Teddy ASSIH</dc:creator>
      <pubDate>Thu, 24 Oct 2024 05:51:27 +0000</pubDate>
      <link>https://dev.to/ion_finisher/building-an-faq-generator-api-with-nextjs-gpt-4-and-unkey-making-rate-limiting-fun-p1l</link>
      <guid>https://dev.to/ion_finisher/building-an-faq-generator-api-with-nextjs-gpt-4-and-unkey-making-rate-limiting-fun-p1l</guid>
      <description>&lt;h2&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%2Feromm8yjwt9qpfxr47vm.gif" alt="gif" width="1024" height="1024"&gt;
&lt;/h2&gt;

&lt;p&gt;Who doesn’t appreciate a solid FAQ section? Those little gems of information help users find answers quickly without flooding your inbox with questions. But what if I told you we could automate the FAQ creation process? Even better, we can add some AI flair, link it all to a sleek Next.js app, and keep everything in check with rate limiting using &lt;strong&gt;Unkey&lt;/strong&gt;. Plus, we’ll make sure the API doesn’t munch through your monthly token limit!&lt;/p&gt;

&lt;p&gt;And it's &lt;strong&gt;live &lt;a href="https://askiq.vercel.app/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;Open-Source &lt;a href="https://github.com/Ionfinisher/askiq" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/strong&gt; (don't forget to drop a ⭐️ ;)).&lt;/p&gt;

&lt;p&gt;Sound good? Let’s dive in.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Challenge: Automating FAQs (While Keeping Costs Low)
&lt;/h3&gt;

&lt;p&gt;Imagine you’ve just launched a cool new app, and users are firing off questions like, "What’s this app all about?" or "How does it work?" You can sit down and write answers to all these questions, but what if the app could just generate FAQs for you? Sounds good, right?&lt;/p&gt;

&lt;p&gt;Enter OpenAI’s GPT-4 (or 3.5 if you’re on a budget) to create intelligent, auto-generated FAQs. But there’s a catch: OpenAI’s API isn’t free, and unless you're sitting on piles of money, you're working with a limited budget. You need a smart way to manage how many API requests your users can make.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;Unkey&lt;/strong&gt; comes in. It's a simple API Management tool that provides rate-limiting to prevent any single user from overwhelming your system (or your wallet). Let's see how I built it all!&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Setting Up Next.js and the OpenAI API
&lt;/h3&gt;

&lt;p&gt;First things first: Next.js is our go-to framework for this project. It’s the ship-fasters favorite (yeah, I just created a word :)).&lt;/p&gt;

&lt;p&gt;Here’s a quick breakdown of how we handle the AI magic. In our API route, we send a request to OpenAI’s GPT-4, asking it to generate a list of FAQs based on a topic.&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;rateLimit&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/unkey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parseFaqResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/responseParser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&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;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;topic&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="nx"&gt;request&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="c1"&gt;//   Make sure the 'topic' parameter is provided&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;topic&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;topic&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="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;Missing or invalid 'topic' parameter&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Get the user's IP address&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-forwarded-for&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;anonymous&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Verify the rate limit&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rateLimitResponse&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;rateLimit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;rateLimitResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Return a 429 status response if the limit is exceeded&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API rate limit exceeded. Try again later&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;429&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Generate FAQ answer using GPT-4o&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="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-4o-mini&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;messages&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PROMPT&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Here is the topic: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;topic&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="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFaqResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&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;NextResponse&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="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&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="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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="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;Error generating FAQ&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="na"&gt;status&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="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this API endpoint, we take in the &lt;code&gt;topic&lt;/code&gt; and the &lt;code&gt;user ip address&lt;/code&gt; (to check rate limits). We send the topic to GPT-4, ask it for some FAQs, and then return a beautifully formatted JSON response. And if the user goes crazy with requests? Unkey steps in and says, "Whoa there, slow down!"&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Rate Limiting with Unkey – Because No One Likes an Overachiever
&lt;/h3&gt;

&lt;p&gt;You might think rate limiting is boring, but it’s a lifesaver, especially when you’re using paid services like OpenAI. Unkey is simple and handles this gracefully. &lt;/p&gt;

&lt;p&gt;Here's what makes Unkey awesome:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's dead simple&lt;/strong&gt; to use (seriously, you don’t need to know how to center a div).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Friendly pricing&lt;/strong&gt; – Unkey doesn’t break the bank, and it keeps your OpenAI API budget in check.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our setup, we allow 2 requests per 10s, per user. After that, Unkey jumps in and throws a friendly "Rate limit exceeded" message at them within that 10s window. This keeps your API from getting overwhelmed and prevents your token count from going over budget. Here is what the &lt;code&gt;unkey.ts&lt;/code&gt; file looks like:&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Ratelimit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@unkey/ratelimit&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;unkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Ratelimit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;rootKey&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;UNKEY_ROOT_KEY&lt;/span&gt;&lt;span class="o"&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;10s&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;askiq&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;async&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="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;unkey&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Error Handling – Because Things Go Wrong Sometimes
&lt;/h3&gt;

&lt;p&gt;Building an API isn’t just about making things work—it's about making sure things &lt;em&gt;don’t break&lt;/em&gt; when users do the unexpected. &lt;/p&gt;

&lt;p&gt;Here are some of the things we check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing &lt;code&gt;topic&lt;/code&gt;? We send a 400 error.&lt;/li&gt;
&lt;li&gt;Rate limit exceeded? We send a 429 error.&lt;/li&gt;
&lt;li&gt;Anything else? We send a 500 error and say, “Oops, something went wrong.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s all about keeping things smooth and user-friendly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Result: A Developer-Friendly FAQ Generator
&lt;/h3&gt;

&lt;p&gt;At the end of the day, what do we have? A sleek, rate-limited FAQ generator that developers can use to get auto-generated FAQs about any topic they want, powered by OpenAI’s GPT-4 called &lt;a href="https://askiq.vercel.app/" rel="noopener noreferrer"&gt;ASKIQ&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So there you have it: a slick FAQ generator that uses AI to save you time, powered by an efficient rate-limiting system. Want to keep your API in check? Give &lt;strong&gt;&lt;a href="https://unkey.com/" rel="noopener noreferrer"&gt;Unkey&lt;/a&gt;&lt;/strong&gt; a spin!&lt;/p&gt;

&lt;p&gt;Now, off you go to build your own open-source project or take this &lt;a href="https://github.com/Ionfinisher/askiq" rel="noopener noreferrer"&gt;one&lt;/a&gt; to the next level and you might as well leave a star since you're there. Happy coding!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>unkey</category>
      <category>api</category>
    </item>
    <item>
      <title>Shipping a Nextjs app in two days with Hanko Auth elements</title>
      <dc:creator>Teddy ASSIH</dc:creator>
      <pubDate>Fri, 18 Oct 2024 09:52:05 +0000</pubDate>
      <link>https://dev.to/ion_finisher/shipping-a-nextjs-app-in-two-days-with-hanko-auth-elements-4o8e</link>
      <guid>https://dev.to/ion_finisher/shipping-a-nextjs-app-in-two-days-with-hanko-auth-elements-4o8e</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggvkhe950ptfp6gd0oxg.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%2Fggvkhe950ptfp6gd0oxg.gif" alt="Hanko Gif" width="500" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;As part of building modern web applications, authentication is a critical aspect, and developers often seek secure, user-friendly, and flexible solutions to handle it. In my recent Next.js project, I chose to integrate &lt;strong&gt;Hanko&lt;/strong&gt;, an authentication provider that allow you integrate auth and passkeys in minutes.&lt;/p&gt;

&lt;p&gt;In this article, I’ll walk you through how I used Hanko in my app to implement secure, both password and passwordless authentication for users, cause yes, Hanko can do both :).&lt;/p&gt;




&lt;h3&gt;
  
  
  Why I Chose Hanko
&lt;/h3&gt;

&lt;p&gt;Hanko is a powerful authentication solution that provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free 10,000 monthly active users:&lt;/strong&gt; You can start for free with passkeys, social logins, email passcodes, optional passwords and user management.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Passwordless authentication&lt;/strong&gt;: No need to store passwords, reducing security risks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy to understand an clear documentation&lt;/strong&gt;: Hanko has probably the best documentation I've ever seen. It guets you started quickly with their framework specific quickstarts so that you can ship fast.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given the project’s focus on security and ease of use, Hanko was the perfect choice for my app.&lt;/p&gt;




&lt;h3&gt;
  
  
  What is my App about?
&lt;/h3&gt;

&lt;p&gt;To Don't App is a not so common to do App where instead of adding the tasks you have to do, you add the ones or the habits you have to avoid. The Hanko auth here helps keeping the user data safe as it is persisted in a mongodb database.&lt;br&gt;
It's Live &lt;strong&gt;&lt;a href="https://next-todont.vercel.app/" rel="noopener noreferrer"&gt;here🎉&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step-by-Step Integration of Hanko in my App
&lt;/h3&gt;

&lt;p&gt;Here’s how I integrated Hanko into my To Don't app:&lt;/p&gt;
&lt;h4&gt;
  
  
  1. &lt;strong&gt;Setting Up the Next.js Project&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;I started by creating the Next.js project. If you haven't done so already, you can set up a new Next.js project using the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app@latest next-todont-app
&lt;span class="nb"&gt;cd &lt;/span&gt;next-todont-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. &lt;strong&gt;Creating an Account on Hanko&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;To integrate Hanko, I first needed to set up a Hanko account. I signed up on their platform and created a new project to obtain the necessary API URL for authentication.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. &lt;strong&gt;Sign Up&lt;/strong&gt; at &lt;a href="https://hanko.io" rel="noopener noreferrer"&gt;Hanko&lt;/a&gt;.
&lt;/h4&gt;

&lt;h4&gt;
  
  
  2. Create a new project and retrieve your &lt;strong&gt;API URL&lt;/strong&gt;.
&lt;/h4&gt;

&lt;h4&gt;
  
  
  3. Installing Hanko SDK
&lt;/h4&gt;

&lt;p&gt;I installed the Hanko SDK in my Next.js app to handle the authentication flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @teamhanko/hanko-elements
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This package provides the client-side tools for registering, authenticating, and managing users.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Setting environment variables
&lt;/h4&gt;

&lt;p&gt;I created an &lt;code&gt;.env.local&lt;/code&gt; file in my project root and added my Hanko API URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NEXT_PUBLIC_HANKO_API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://f4&lt;span class="k"&gt;****&lt;/span&gt;&lt;span class="nt"&gt;-4802-49ad-8e0b-3d3&lt;/span&gt;&lt;span class="k"&gt;****&lt;/span&gt;ab32.hanko.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that the authentication service can securely communicate with Hanko's API.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. &lt;strong&gt;Building the Authentication Pages&lt;/strong&gt;
&lt;/h4&gt;

&lt;h5&gt;
  
  
  &lt;strong&gt;Login&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;To allow users to register and login, I created a page integrating the &lt;code&gt;&amp;lt;hanko-auth&amp;gt;&lt;/code&gt; web component that adds these two interfaces. &lt;/p&gt;

&lt;p&gt;First, I imported the register function from &lt;code&gt;@teamhanko/hanko-elements&lt;/code&gt; into my Next.js component. Called it with the Hanko API URL as an argument to register &lt;code&gt;&amp;lt;hanko-auth&amp;gt;&lt;/code&gt;. Once done, I added it in my JSX. Here's a snippet for the component:&lt;br&gt;
&lt;code&gt;components/HankoAuth.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Hanko&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@teamhanko/hanko-elements&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;hankoApi&lt;/span&gt; &lt;span class="o"&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;NEXT_PUBLIC_HANKO_API_URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HankoAuth&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&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;hanko&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setHanko&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Hanko&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setHanko&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hanko&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hankoApi&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;redirectAfterLogin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&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="c1"&gt;// successfully logged in, redirect to a page in your application&lt;/span&gt;
    &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboard&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="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;hanko&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;onSessionCreated&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="nf"&gt;redirectAfterLogin&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;hanko&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;redirectAfterLogin&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&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="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hankoApi&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// handle 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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;hanko&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I added the component to the login page:&lt;br&gt;
&lt;code&gt;app/login/page.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;HankoAuth&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/components/HankoAuth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;LoginPage&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="p"&gt;(&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex justify-center items-center h-screen"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HankoAuth&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
 &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And BOOM 🎉. Just like that, the sign in and sign up features are done. Here is what it looks like 👇&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%2Ftffo2whfuqp7ixcvhvld.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%2Ftffo2whfuqp7ixcvhvld.png" alt="Login Page" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;strong&gt;Profile Page&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;For users to see their account infos, I created a page using the &lt;code&gt;&amp;lt;hanko-profile&amp;gt;&lt;/code&gt; component. This component provides an interface, where users can manage their email addresses and passkeys. &lt;br&gt;
&lt;code&gt;components/HankoProfile.jsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@teamhanko/hanko-elements&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;hankoApi&lt;/span&gt; &lt;span class="o"&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;NEXT_PUBLIC_HANKO_API_URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HankoProfile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&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="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hankoApi&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// handle 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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;hanko&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="na"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is Profile page:&lt;br&gt;
&lt;code&gt;app/profile/page.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;HankoProfile&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/components/profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProfilePage&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HankoProfile&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at this beauty :):&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%2Fb6y7fewukz0sayec2ceu.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%2Fb6y7fewukz0sayec2ceu.png" alt="profile page" width="800" height="637"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  &lt;strong&gt;Logout&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;Now for the Logout. I used &lt;code&gt;@teamhanko/hanko-elements&lt;/code&gt; to easily manage user logouts. Below, I created a logout button component I used on my dashboard.&lt;br&gt;
&lt;code&gt;components/LogoutButton.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hanko&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@teamhanko/hanko-elements&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;hankoApi&lt;/span&gt; &lt;span class="o"&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;NEXT_PUBLIC_HANKO_API_URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;LogoutBtn&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&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;hanko&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setHanko&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Hanko&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setHanko&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hanko&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hankoApi&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logout&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="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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hanko&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;router&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&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="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 during 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;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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;logout&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  &lt;strong&gt;Customization&lt;/strong&gt;
&lt;/h5&gt;

&lt;p&gt;To make it look &lt;code&gt;seggsy&lt;/code&gt;, I customized the Hanko elements to make the app's UI uniform. To achieve this, i created a dedicated CSS file for the Hanko elements. For further explanation, I really recommend these two articles: &lt;a href="https://docs.hanko.io/guides/hanko-elements/customize-appearance" rel="noopener noreferrer"&gt;Hanko Docs&lt;/a&gt; and &lt;a href="https://dev.to/hanko/make-hanko-components-shine-1d61"&gt;Esther-Lita post&lt;/a&gt;&lt;br&gt;
&lt;code&gt;src/app/hanko.css&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#111827&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--color-shade-1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#222222&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--color-shade-2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#eff5ff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--brand-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#000000&lt;/span&gt;&lt;span class="n"&gt;e8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--headline1-font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;29px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--headline2-font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--headline1-margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt; &lt;span class="m"&gt;40px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--item-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;36px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--link-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#111827&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--divider-padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;15px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;hanko-auth&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--container-max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;640px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--container-padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;40px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;hanko-profile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--container-max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;550px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;--container-padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nt"&gt;hanko-profile&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;container&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="m"&gt;#d8dee3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;hanko-auth&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#848587&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#848587&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nt"&gt;hanko-auth&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;headline1&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
&lt;span class="nt"&gt;hanko-profile&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;headline1&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
&lt;span class="nt"&gt;hanko-auth&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;headline2&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
&lt;span class="nt"&gt;hanko-profile&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;headline2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1f2328&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;36px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;hanko-auth&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;form-item&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;hanko-auth&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
&lt;span class="nt"&gt;hanko-profile&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;hanko-auth&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;secondary-button&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--brand-color--&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;hanko-auth&lt;/span&gt;&lt;span class="nd"&gt;::part&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;divider&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--brand-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is what it looks like now:&lt;br&gt;
Login:&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%2F4po645ojkocd5mdsh7ze.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%2F4po645ojkocd5mdsh7ze.png" alt="Login after custom" width="800" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Profile:&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%2Fvb6l9g9wcjjuajg2gzfp.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%2Fvb6l9g9wcjjuajg2gzfp.png" alt="profile after custom" width="600" height="557"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  6. &lt;strong&gt;Securing API Routes&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;I ensured that sensitive API routes (like those managing to-do items) were protected by checking whether the user was authenticated before allowing access. This was done by adding middleware that verifies if there's valid JWT and using the jose library. However, you’re free to choose any other suitable library.&lt;br&gt;
&lt;code&gt;middleware.ts&lt;/code&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;jwtVerify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createRemoteJWKSet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jose&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;hankoApiUrl&lt;/span&gt; &lt;span class="o"&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;NEXT_PUBLIC_HANKO_API_URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&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;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&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;hanko&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hanko&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;JWKS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRemoteJWKSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;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="nx"&gt;hankoApiUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.well-known/jwks.json`&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verifiedJWT&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;jwtVerify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hanko&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JWKS&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;matcher&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="s2"&gt;profile&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/dashboard&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;p&gt;This middleware ensures that only authenticated users can access certain API endpoints.&lt;/p&gt;

&lt;h4&gt;
  
  
  7. &lt;strong&gt;Getting User data&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;To Link th "to-donts" to a specific user, I needed to get the user Id from Hanko. To achieve that, I created a custom hook to fetch the current user’s data  using &lt;code&gt;@teamhanko/hanko-elements&lt;/code&gt;. The useUserData hook leverages the hanko.user.getCurrent() method to retrieve the currently logged-in user’s data. I just needed the user Id, but you can get more.&lt;br&gt;
&lt;code&gt;hooks/useUserData.ts&lt;/code&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hanko&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@teamhanko/hanko-elements&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;hankoApi&lt;/span&gt; &lt;span class="o"&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;NEXT_PUBLIC_HANKO_API_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// I just needed the user id here&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;HankoUser&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useUserData&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;HankoUser&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;hanko&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setHanko&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Hanko&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;userState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUserState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HankoUser&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;id&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;loading&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;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setHanko&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hanko&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hankoApi&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;hanko&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;id&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="nf"&gt;setUserState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;loading&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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setUserState&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prevState&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;prevState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;loading&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="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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hanko&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;userState&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;And here, I used it in my dashboard page:&lt;br&gt;
&lt;code&gt;src/app/dashboard/page.tsx&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useUserData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/hooks/useUserData&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useSessionData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/hooks/useSessionData&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;Dashboard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userDataLoading&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="nx"&gt;userDataError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUserData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userDataLoading&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;User id: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  8. &lt;strong&gt;Deploying on Vercel&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;After testing the app locally, I deployed it to Vercel for production. Here's how I did it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Commit and push the code to a GitHub repository.&lt;/li&gt;
&lt;li&gt;Link the repository to Vercel for automatic deployment.&lt;/li&gt;
&lt;li&gt;Configure the &lt;strong&gt;environment variables&lt;/strong&gt; (Hanko API URL and database related variables) in the Vercel dashboard.&lt;/li&gt;
&lt;li&gt;Deploy the app.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Integrating Hanko in my Next.js app made the authentication process much more secure, user-friendly and fast. With the rise of passwordless authentication and biometrics, Hanko’s approach provides an excellent way to enhance security while offering users a simple and modern login experience.&lt;/p&gt;

&lt;p&gt;If you're looking for a fast authentication solution in your Next.js project, I highly recommend giving Hanko a try! Try &lt;strong&gt;&lt;a href="https://hanko.io/" rel="noopener noreferrer"&gt;Hanko now&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>hanko</category>
      <category>vercel</category>
    </item>
    <item>
      <title>Making Passwords Optional: What You Need to Know</title>
      <dc:creator>Teddy ASSIH</dc:creator>
      <pubDate>Tue, 15 Oct 2024 08:45:46 +0000</pubDate>
      <link>https://dev.to/ion_finisher/making-passwords-optional-what-you-need-to-know-59gn</link>
      <guid>https://dev.to/ion_finisher/making-passwords-optional-what-you-need-to-know-59gn</guid>
      <description>&lt;p&gt;As technology advances, the way we secure our online accounts is changing. Traditional passwords are increasingly seen as outdated and insecure due to risks like phishing and data breaches. Many companies are now adopting passwordless login methods, such as biometrics and one-time codes, allowing users to remove passwords entirely.&lt;/p&gt;

&lt;p&gt;This article will explore the important factors to consider when giving users the option to delete their passwords.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why Consider Making Passwords Optional?
&lt;/h3&gt;

&lt;p&gt;There are several benefits to reducing reliance on passwords:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Better Security&lt;/strong&gt;: Passwordless methods, like biometrics or magic links, lower the chances of falling victim to phishing or other attacks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easier User Experience&lt;/strong&gt;: Remembering complex passwords can be a hassle. Removing them simplifies the login process, making it faster and more user-friendly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reduced Support Costs&lt;/strong&gt;: A significant number of helpdesk requests involve password resets. Eliminating passwords can help cut down these costs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, allowing users to delete their passwords requires careful planning to ensure security and usability.&lt;/p&gt;




&lt;h3&gt;
  
  
  Key Factors When Users Delete Their Passwords
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. &lt;strong&gt;Offer Strong Alternatives&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;When users opt out of passwords, they need secure and convenient alternatives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Biometric Authentication&lt;/strong&gt;: This includes face recognition or fingerprint scanning.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Passkeys&lt;/strong&gt;: These are cryptographic keys stored on devices that authenticate users without needing a password. Check &lt;a href="https://www.passkeys.io/" rel="noopener noreferrer"&gt;passkey.io&lt;/a&gt;, an interactive passkeys demo and info page for a better understanding of this concept.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Magic Links or One-Time Passwords (OTP)&lt;/strong&gt;: These are temporary codes sent via email or text that allow access.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's crucial to choose methods that provide strong security. For instance, while passkeys are resistant to phishing, magic links can be compromised if someone accesses the user's email.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. &lt;strong&gt;User Experience Matters&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;While removing passwords can enhance user experience, alternatives must be easy to use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fallback Options&lt;/strong&gt;: Provide backup methods for those who may struggle with biometric systems or passkeys. Options like one-time codes can help ensure everyone can access their accounts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cross-Device Access&lt;/strong&gt;: Make sure users can log in easily across different devices. For example, passkeys should sync between a user’s phone and computer for seamless access.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. &lt;strong&gt;Account Recovery Options&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;If users delete their passwords, having reliable recovery options is essential:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secure Recovery Methods&lt;/strong&gt;: Offer various ways for users to regain access if they lose their authentication method, such as backup codes or email recovery links.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These recovery options must be secure against unauthorized access, and users should be advised on how to safely store backup codes.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. &lt;strong&gt;Multi-Factor Authentication (MFA)&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;For sensitive actions, even a passwordless system may require additional verification:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Optional MFA&lt;/strong&gt;: Allow users to enable MFA for added security during critical actions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Contextual MFA&lt;/strong&gt;: Implement MFA only for high-risk activities or unfamiliar login attempts.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  5. &lt;strong&gt;Empower Users with Control&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Give users control over their authentication methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clear Communication&lt;/strong&gt;: Ensure users understand their options regarding password deletion and what alternatives are available.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Notifications&lt;/strong&gt;: Inform users immediately if they change their authentication method to prevent unauthorized changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Easy Re-enabling of Passwords&lt;/strong&gt;: Allow users to reinstate their passwords easily if they encounter issues with alternative methods.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  6. &lt;strong&gt;Compatibility with Older Systems&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Not all systems support modern authentication methods yet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Graceful Degradation&lt;/strong&gt;: Ensure that users on older devices can still log in using traditional passwords if needed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Device Compatibility&lt;/strong&gt;: Confirm that your authentication methods work across various devices and operating systems.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  7. &lt;strong&gt;Compliance with Regulations&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Adhering to legal standards is crucial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Regulatory Compliance&lt;/strong&gt;: Make sure your system complies with regulations like GDPR or HIPAA when handling user data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Best Practices for Data Security&lt;/strong&gt;: Follow industry guidelines for encrypting authentication data and securely storing sensitive information.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Making passwords optional is a progressive move toward better account security and user satisfaction. However, it requires thoughtful planning to ensure users have reliable alternatives like biometrics or OTPs. Key considerations include providing strong alternatives, ensuring a smooth user experience, establishing secure recovery options, and maintaining compliance with regulations.&lt;/p&gt;

&lt;p&gt;To facilitate this transition effectively, consider using tools like &lt;a href="https://www.hanko.io/" rel="noopener noreferrer"&gt;Hanko.io&lt;/a&gt;. Hanko offers robust solutions for implementing passwordless authentication seamlessly into your applications with their passkey API. By leveraging their technology, you can enhance security while simplifying the user experience. &lt;/p&gt;

&lt;p&gt;Explore more about how Hanko can help you make the shift towards passwordless authentication &lt;a href="https://hanko.io" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>hanko</category>
      <category>passkey</category>
      <category>authentication</category>
    </item>
    <item>
      <title>Hacktoberfest 🎃 oss.gg: Code, Conquer, and Win Big! 💻🎮📱</title>
      <dc:creator>Teddy ASSIH</dc:creator>
      <pubDate>Fri, 04 Oct 2024 20:02:11 +0000</pubDate>
      <link>https://dev.to/ion_finisher/hacktoberfest-ossgg-code-conquer-and-win-big-5931</link>
      <guid>https://dev.to/ion_finisher/hacktoberfest-ossgg-code-conquer-and-win-big-5931</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fui1dbyseaksefxsw31tl.jpg" 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%2Fui1dbyseaksefxsw31tl.jpg" alt="Image description" width="500" height="506"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Hello, Open Source Enthusiasts! 👋&lt;/p&gt;

&lt;p&gt;Hacktoberfest 2024 has officially kicked off, and &lt;a href="https://oss.gg/signup" rel="noopener noreferrer"&gt;oss.gg&lt;/a&gt; is excited to present a thrilling gamified experience for those looking to dive into their first open-source contributions and get all the fun from open source. This is a fun way to for participants to engage in both coding and non-coding tasks, earning points that serve as lottery tickets for exciting prizes at the end of the month.&lt;/p&gt;




&lt;h2&gt;
  
  
  What’s at stake? 🏆
&lt;/h2&gt;

&lt;p&gt;You can look forward to an array of fantastic prizes, including:&lt;br&gt;
MacBook Pro 2023 M3 14 💻&lt;br&gt;
MacBook Air 2023 M3 15 💻&lt;br&gt;
iPhone 16 (512GB) 📱&lt;br&gt;
PlayStation 5 🎮&lt;br&gt;
AirPods Pro (3rd Gen) (x2) 🎧&lt;br&gt;
EPOMAKER EP84 Keyboard ⌨️&lt;br&gt;
EPOMAKER x AULA F75 Keyboards (x2) ⌨️&lt;br&gt;
A quirky brick 🧱 (Yes, you read that right!)&lt;/p&gt;

&lt;p&gt;Here is how and where you can start 👇&lt;/p&gt;




&lt;h2&gt;
  
  
  1. &lt;a href="https://formbricks.com/" rel="noopener noreferrer"&gt;FormBricks&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fformbricks.com%2F_next%2Fstatic%2Fmedia%2Ffooterlogo.e272c0f1.svg" 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%2Fformbricks.com%2F_next%2Fstatic%2Fmedia%2Ffooterlogo.e272c0f1.svg" alt="formbricks-logo" width="725" height="150"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;: TypeScript, MDX, JavaScript, Dockerfile&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target Audience&lt;/strong&gt;: UI/UX Designers, Survey Creators, Marketers&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;: FormBricks is an open-source platform for creating and analyzing surveys with a no-code editor and customizable templates. It integrates with tools like Slack and Notion and supports self-hosting via Docker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contribution Opportunities&lt;/strong&gt;:&lt;br&gt;
Develop new integrations with external tools.&lt;br&gt;
Enhance existing features or add new template formats.&lt;br&gt;
Fix bugs and optimize performance.&lt;br&gt;
Create documentation and tutorials.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. &lt;a href="https://openbb.co/" rel="noopener noreferrer"&gt;OpenBB&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;br&gt;&lt;br&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%2F31pjj90eaqljix6orj39.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%2F31pjj90eaqljix6orj39.png" alt="openbb-logo" width="380" height="112"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;: Python&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target Audience&lt;/strong&gt;: Data Analysts, Finance Researchers&lt;br&gt;
Overview: OpenBB simplifies access to financial data through a unified API, allowing users to retrieve information effortlessly from multiple sources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contribution Opportunities&lt;/strong&gt;:&lt;br&gt;
Integrate new financial data providers.&lt;br&gt;
Enhance analytical tools for better user experience.&lt;br&gt;
Collaborate on bug fixes.&lt;br&gt;
Write documentation and tutorials.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. &lt;a href="https://www.unkey.com/" rel="noopener noreferrer"&gt;Unkey&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;br&gt;&lt;br&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%2F3faz2vaumyr9w3fid0z7.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%2F3faz2vaumyr9w3fid0z7.png" alt="unkey-logo" width="400" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;: TypeScript, Go, React, Next.js, Turborepo&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target Audience&lt;/strong&gt;: Developers in API Management and DevOps&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;: Unkey offers a straightforward API management platform with features like key management and analytics for large language models (LLM).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contribution Opportunities&lt;/strong&gt;:&lt;br&gt;
Introduce new feature types or targeting rules.&lt;br&gt;
Develop SDKs for additional programming languages.&lt;br&gt;
Improve the web dashboard usability.&lt;br&gt;
Document features with examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. &lt;a href="https://papermark.io/" rel="noopener noreferrer"&gt;Papermark&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;br&gt;&lt;br&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%2Fi5jlh6fs6in9misco51p.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%2Fi5jlh6fs6in9misco51p.png" alt="papermark-logo" width="318" height="138"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;: TypeScript&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target Audience&lt;/strong&gt;: Privacy-Conscious Document Sharers&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;: Papermark.io provides a secure document-sharing alternative with analytics support and custom branding options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contribution Opportunities&lt;/strong&gt;:&lt;br&gt;
Develop tagging or categorization features.&lt;br&gt;
Create browser extensions for easier content saving.&lt;br&gt;
Build a mobile app for document access on the go.&lt;br&gt;
Enhance documentation and localization efforts.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;a href="https://twenty.com/" rel="noopener noreferrer"&gt;Twenty CRM&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;br&gt;&lt;br&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%2F9efm9lwbpcqe980fkw09.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%2F9efm9lwbpcqe980fkw09.png" alt="twenty-crm-logo" width="192" height="192"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;: React, Apollo, GraphQL Codegen, Recoil, TypeScript, Jest, Storybook&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target Audience&lt;/strong&gt;: Business Dashboard Developers&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;: Twenty.com is an open-source CRM platform offering customizable solutions for managing customer relationships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contribution Opportunities&lt;/strong&gt;:&lt;br&gt;
Build new modules for various business functions.&lt;br&gt;
Create integrations with third-party services.&lt;br&gt;
Improve UI/UX for better user experience.&lt;br&gt;
Contribute to user guides and developer documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. &lt;a href="https://www.hanko.io/" rel="noopener noreferrer"&gt;Hanko&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;br&gt;&lt;br&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%2Fozgtef23w89jyvov4w3l.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%2Fozgtef23w89jyvov4w3l.png" alt="hanko-logo" width="256" height="256"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;: Go, TypeScript, Svelte&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target Audience&lt;/strong&gt;: Authentication and Security Developers&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;: Hanko eliminates passwords through secure passkey-based logins with easy integration options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contribution Opportunities&lt;/strong&gt;:&lt;br&gt;
Support additional authentication methods.&lt;br&gt;
Conduct security audits to identify vulnerabilities.&lt;br&gt;
Develop SDKs for easier integration.&lt;br&gt;
Write comprehensive setup guides and API documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. &lt;a href="https://dub.co/" rel="noopener noreferrer"&gt;Dub.co&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;br&gt;&lt;br&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%2F7ivck5zufg20t8svj08d.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%2F7ivck5zufg20t8svj08d.png" alt="dub-logo" width="400" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;: Next.js, TypeScript, Tailwind&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Target Audience&lt;/strong&gt;: UI/UX Designers and Marketers&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;: Dub.co provides link management infrastructure tailored for marketing teams with advanced analytics capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contribution Opportunities&lt;/strong&gt;:&lt;br&gt;
Develop new functionalities like custom domains or QR code creation.&lt;br&gt;
Update documentation for newcomers.&lt;br&gt;
Assist in bug fixes to enhance reliability.&lt;br&gt;
Improve UI/UX design.&lt;/p&gt;

&lt;p&gt;There are lot of fun in those challenges and awesome prizes to win. You can read more &lt;a href="https://oss.gg/signup" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>hacktoberfest</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
