<?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: Sojin Samuel</title>
    <description>The latest articles on DEV Community by Sojin Samuel (@sojinsamuel).</description>
    <link>https://dev.to/sojinsamuel</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%2F872759%2F0efe743b-bc0d-4970-8a40-1144077c7be0.jpg</url>
      <title>DEV Community: Sojin Samuel</title>
      <link>https://dev.to/sojinsamuel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sojinsamuel"/>
    <language>en</language>
    <item>
      <title>My Journey Deploying a Full-Stack Node.js App to AWS using Pulumi</title>
      <dc:creator>Sojin Samuel</dc:creator>
      <pubDate>Sun, 06 Apr 2025 20:22:03 +0000</pubDate>
      <link>https://dev.to/sojinsamuel/pulumi-v2-4fai</link>
      <guid>https://dev.to/sojinsamuel/pulumi-v2-4fai</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;: Fast Static Website Deployment&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;In this tutorial, we'll deploy a dynamic Node.js web application that converts uploaded video files (MP4) into downloadable MP3 audio. The app provides a simple web interface, uses &lt;code&gt;ffmpeg&lt;/code&gt; on the server for conversion, and even generates shareable links. &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%2F09p2lam9mqgkikx2k8ik.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%2F09p2lam9mqgkikx2k8ik.png" alt="Video to mp3 converter"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll use Pulumi with TypeScript to provision the necessary AWS infrastructure: a VPC, an EC2 instance running Ubuntu to host our app, an Application Load Balancer (ALB) to distribute traffic, and the required Security Groups.&lt;/p&gt;

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

&lt;p&gt;You can try the deployed &lt;a href="http://webapp-lb-b17ede9-1163604889.ap-southeast-2.elb.amazonaws.com/" rel="noopener noreferrer"&gt;cloud version of Video-to-MP3 converter here&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Complete Source code of the deployed project:&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/sojinsamuel" rel="noopener noreferrer"&gt;
        sojinsamuel
      &lt;/a&gt; / &lt;a href="https://github.com/sojinsamuel/pulumi-video-converter-deploy" rel="noopener noreferrer"&gt;
        pulumi-video-converter-deploy
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Video to Mp3 converter Project Deployed to AWS via Pulumi
    &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;☁️🚀 Deploy Video-to-MP3 Converter to AWS with Pulumi 🎬🎵&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This repository contains the Pulumi infrastructure code (IaC) written in TypeScript to deploy the &lt;a href="https://github.com/sojinsamuel/video-to-mp3-app-example" rel="noopener noreferrer"&gt;Node.js Video-to-MP3 Converter application&lt;/a&gt; to AWS.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Goal:&lt;/strong&gt; To automate the provisioning of AWS resources (EC2, Application Load Balancer, Security Groups, VPC configuration) required to host and run the web application, making deployment repeatable and manageable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;📖 Companion Tutorial:&lt;/strong&gt; This repository is best understood alongside the detailed tutorial available on dev.to
&lt;strong&gt;[&lt;a href="https://dev.to/sojinsamuel/pulumi-v2-4fai" rel="nofollow"&gt;https://dev.to/sojinsamuel/pulumi-v2-4fai&lt;/a&gt;]&lt;/strong&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🏗️ Architecture Overview&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;This Pulumi program deploys the following AWS resources:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Networking:&lt;/strong&gt; Uses your AWS account's &lt;strong&gt;Default VPC&lt;/strong&gt; and associated &lt;strong&gt;Subnets&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Groups:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;alb-sg&lt;/code&gt;: Allows public HTTP traffic (port 80) to the Load Balancer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;instance-sg&lt;/code&gt;: Allows traffic from the ALB to the EC2 instance on the application port (3001) and allows SSH access (port 22) for management (&lt;strong&gt;Important:&lt;/strong&gt; Restrict SSH source IP!). Allows all outbound traffic.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute:&lt;/strong&gt; An &lt;strong&gt;EC2&lt;/strong&gt;…&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/sojinsamuel/pulumi-video-converter-deploy" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Tutorial Starter Repo
&lt;/h2&gt;

&lt;p&gt;For this tutorial, &lt;a href="https://github.com/sojinsamuel/video-to-mp3-app-example" rel="noopener noreferrer"&gt;we'll be using this repo as the starter code&lt;/a&gt;, which I've already created for you. It doesn't use Pulumi or AWS as a cloud service, which is exactly what we'll be adding today. We'll come back to it later as our application code to follow along with the tutorial.&lt;/p&gt;

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

&lt;p&gt;Moving beyond simple static sites, this project tackles deploying a real-world Node.js application. We need a server, file processing, and external API interaction. &lt;/p&gt;

&lt;p&gt;This guide walks through cloning the application code, setting up a &lt;em&gt;separate&lt;/em&gt; Pulumi project to define our AWS infrastructure, configuring Git, and finally deploying everything using Pulumi's Infrastructure as Code approach.&lt;/p&gt;

&lt;p&gt;We'll focus on clarity and explain the "what" and "why" behind each step, especially for those new to Pulumi or AWS cloud deployments. &lt;/p&gt;

&lt;p&gt;So, in this article, we'll cover:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Setting up the project structure locally.&lt;/li&gt;
&lt;li&gt; Cloning the application code.&lt;/li&gt;
&lt;li&gt; Optionally running the app locally.&lt;/li&gt;
&lt;li&gt; Initializing a separate Pulumi project for infrastructure.&lt;/li&gt;
&lt;li&gt; Writing the Pulumi code (Infrastructure as Code) in TypeScript.&lt;/li&gt;
&lt;li&gt; Handling Git repository setup for deployment.&lt;/li&gt;
&lt;li&gt; Deploying to AWS using &lt;code&gt;pulumi up&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Why Pulumi?&lt;/strong&gt; Pulumi lets us define cloud infrastructure using familiar programming languages like TypeScript. This improves productivity, enables version control, and fits naturally into modern development workflows. It supports CI/CD integration, encourages better collaboration, and works across multiple cloud providers; making infrastructure easier to manage, automate, and scale.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://signin.aws.amazon.com/signup?request_type=register" rel="noopener noreferrer"&gt;An AWS account&lt;/a&gt; with configured credentials (IAM user or default profile with permissions for EC2, VPC, ALB).&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html#getting-started-install-instructions" rel="noopener noreferrer"&gt;AWS CLI installed&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://www.pulumi.com/docs/iac/download-install/" rel="noopener noreferrer"&gt;Pulumi CLI installed&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://nodejs.org/en/download" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; (v18+ recommended, I used v22.x.x).&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git installed&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;  A &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub account&lt;/a&gt; to host your code repository.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 1: Create Your Main Project Directory
&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%2Fm7mbvrhxyitym5r7shtg.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%2Fm7mbvrhxyitym5r7shtg.gif" alt="Change directory"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, let's create a parent directory on your local machine to hold both the application code and the infrastructure code. This keeps everything organized.&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="nb"&gt;mkdir &lt;/span&gt;pulumi-video-converter &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;pulumi-video-converter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Why?&lt;/em&gt; This top-level folder will be our Git repository root later, containing both the app and pulumi IaC subdirectory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Clone the Application Code
&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%2Ft9gc8esw8xesxu8cojy4.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%2Ft9gc8esw8xesxu8cojy4.gif" alt="Clone app repo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside the &lt;code&gt;pulumi-video-converter&lt;/code&gt; directory, clone the GitHub repository containing the application code, i created for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Tutorial starter code!&lt;/span&gt;
git clone https://github.com/sojinsamuel/video-to-mp3-app-example.git video-to-mp3-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a subdirectory &lt;code&gt;video-to-mp3-app&lt;/code&gt; containing our app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi-video-converter/
└── video-to-mp3-app/     &lt;span class="c"&gt;# &amp;lt;-- Cloned application code&lt;/span&gt;
    ├── public/
    ├── .git/             &lt;span class="c"&gt;# &amp;lt;-- Will be removed in next step&lt;/span&gt;
    ├── .gitignore
    ├── server.js
    ├── package.json
    └── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Remove Nested &lt;code&gt;.git&lt;/code&gt; Directory (Important!)
&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%2Fz6q53y2oxfxuca1bx5cf.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%2Fz6q53y2oxfxuca1bx5cf.gif" alt="Remove git folder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;video-to-mp3-app&lt;/code&gt; directory you just cloned contains its own &lt;code&gt;.git&lt;/code&gt; history from the original repo. To avoid issues with Git treating this as a submodule or causing conflicts when we initialize Git for our main &lt;code&gt;pulumi-video-converter&lt;/code&gt; project, remove the nested &lt;code&gt;.git&lt;/code&gt; folder:&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="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; video-to-mp3-app/.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Why?&lt;/em&gt; This ensures that &lt;code&gt;video-to-mp3-app&lt;/code&gt; is treated as just a regular subdirectory within our main project's Git repository, which we'll set up later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Install App Dependencies &amp;amp; Test Locally (Optional)
&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%2F3wk2mx45qokk79zpcq8p.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%2F3wk2mx45qokk79zpcq8p.gif" alt="test app locally"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before deploying, let's ensure the app runs locally.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Navigate into the application directory (if you're not already):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;video-to-mp3-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install dependencies:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the server:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open your browser to &lt;code&gt;http://localhost:3001&lt;/code&gt;. You should see the application interface. Test uploading a small video file if you like.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stop the local server by pressing &lt;code&gt;Ctrl+C&lt;/code&gt; in the terminal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Navigate back to the parent directory:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Why?&lt;/em&gt; This verifies the application code itself is working before we deploy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Initialize Pulumi for Infrastructure
&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%2Fjjam462ennuw3h4cpuny.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%2Fjjam462ennuw3h4cpuny.gif" alt="Initialize pulumi aws typescript boilerplate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, from the root &lt;code&gt;pulumi-video-converter&lt;/code&gt; directory, create a new subdirectory for your infrastructure code:&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="nb"&gt;mkdir &lt;/span&gt;pulumi-infra &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;pulumi-infra
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the &lt;code&gt;pulumi-infra&lt;/code&gt; folder, initialize a new Pulumi project using the AWS TypeScript template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi new aws-typescript &lt;span class="nt"&gt;--stack&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pulumi will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask for basic project details like description and the AWS region where you want to deploy (you can accept the defaults or customize them).&lt;/li&gt;
&lt;li&gt;Generate standard Pulumi files inside the &lt;code&gt;pulumi-infra&lt;/code&gt; folder, including &lt;code&gt;Pulumi.yaml&lt;/code&gt;, &lt;code&gt;Pulumi.dev.yaml&lt;/code&gt;, &lt;code&gt;index.ts&lt;/code&gt;, and more.&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;--stack dev&lt;/code&gt; flag to create and select a stack named &lt;code&gt;dev&lt;/code&gt;. A stack is an isolated instance of your infrastructure (like dev, staging, or production).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your structure now looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pulumi-video-converter/
├── video-to-mp3-app/   # Application code (no .git folder)
└── pulumi-infra/       # Infrastructure code (Pulumi project)
    ├── node_modules/
    ├── Pulumi.yaml
    ├── Pulumi.dev.yaml
    ├── index.ts
    └── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Configure AWS Credentials
&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%2Fsnpxhm4oq087q09r5m9f.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%2Fsnpxhm4oq087q09r5m9f.gif" alt="aws configure command set up"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ensure the AWS CLI (and therefore Pulumi) can authenticate with your AWS account. If you haven't configured the CLI before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll be prompted to enter your AWS Access Key ID, Secret Access Key, and default region (e.g., &lt;code&gt;ap-southeast-2&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This command won't work if the AWS CLI isn't installed. Make sure you’ve installed it first.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why?&lt;/em&gt; Pulumi uses the same credential chain as the AWS CLI to interact with your AWS account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Write the Pulumi Infrastructure Code (&lt;code&gt;index.ts&lt;/code&gt;)
&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%2Farceb8qhn42mg56n5bsa.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%2Farceb8qhn42mg56n5bsa.gif" alt="vim edit file"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navigate into your infrastructure code directory:&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="nb"&gt;cd &lt;/span&gt;pulumi-infra
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, replace the contents of &lt;code&gt;pulumi-infra/index.ts&lt;/code&gt; with the code below. This TypeScript code uses the Pulumi AWS SDK to declare the cloud resources needed.&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;pulumi&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;@pulumi/pulumi&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;aws&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;@pulumi/aws&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;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Instance type for EC2, defaulting to t2.micro (free tier eligible)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instanceType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&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;instanceType&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;t2.micro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// **Placeholder for Our app's GitHub repo URL - we'll set this later!**&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appRepoUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;appRepoUrl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// --- Networking (Using Default VPC) ---&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vpc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getVpc&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vpcId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vpc&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;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;v&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subnetIds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vpc&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;v&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSubnets&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vpc-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;v&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="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;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// --- Security Groups ---&lt;/span&gt;

&lt;span class="c1"&gt;// ___Security Group for the Application Load Balancer (ALB)___&lt;/span&gt;
&lt;span class="c1"&gt;// Allows public HTTP traffic on port 80&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;albSg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SecurityGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alb-sg&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;vpcId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vpcId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Allow HTTP inbound traffic for ALB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="c1"&gt;// Allow HTTP from anywhere&lt;/span&gt;
        &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tcp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fromPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;toPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cidrBlocks&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;0.0.0.0/0&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;egress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="c1"&gt;// Allow all outbound traffic&lt;/span&gt;
        &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fromPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;toPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cidrBlocks&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;0.0.0.0/0&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;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video-to-audio-converter-alb-sg&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="c1"&gt;// ___Security Group for the EC2 Instance___&lt;/span&gt;
&lt;span class="c1"&gt;// Allows traffic from ALB on port 3001 and SSH &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instanceSg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SecurityGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webapp-instance-sg&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;vpcId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vpcId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Allow HTTP from ALB and SSH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ingress&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;// Allow HTTP traffic on port 3001 ONLY from the ALB&lt;/span&gt;
            &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tcp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fromPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;toPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;securityGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;albSg&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="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Allow SSH traffic on port 22 - **IMPORTANT: Restrict this CIDR block!**&lt;/span&gt;
            &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tcp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fromPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;toPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cidrBlocks&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;0.0.0.0/0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;-- Change to your IP/32&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;egress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="c1"&gt;// Allow all outbound traffic (for apt-get, git clone, npm, External APIs)&lt;/span&gt;
        &lt;span class="c1"&gt;// This is a broad rule, consider restricting it further based on your needs&lt;/span&gt;
        &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fromPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;toPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cidrBlocks&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;0.0.0.0/0&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;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video-converter-instance-sg&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="c1"&gt;// --- EC2 Instance ---&lt;/span&gt;

&lt;span class="c1"&gt;// Find the latest Ubuntu 22.04 LTS AMI (Jammy) for amd64&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ami&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAmi&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;filters&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;values&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;ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;virtualization-type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;values&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;hvm&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;mostRecent&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;owners&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;099720109477&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Canonical's AWS account ID&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// **IMPORTANT**: (Assumes you have an existing key pair created in AWS)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keyPairName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keyPairName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`---&amp;gt; DEBUG: Pulumi resolved keyPairName as: '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;keyPairName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interpolate&lt;/span&gt;&lt;span class="s2"&gt;`#!/bin/bash
# Exit on first error
set -e
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; Starting UserData script..."

# --- Install git ---
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; Installing git..."
sudo apt-get update -y &amp;amp;&amp;amp; sudo apt-get install -y git
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; Git installed."

# --- Clone the repo into /home/ubuntu/app ---
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; Cloning repository &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;appRepoUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; into /home/ubuntu/app..."
sudo -u ubuntu git clone &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;appRepoUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; /home/ubuntu/app
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; Clone finished."

# --- Install NVM and Node.js as ubuntu user ---
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; Installing NVM and Node.js 22 for user ubuntu..."
sudo -i -u ubuntu bash &amp;lt;&amp;lt; EOF
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; Running as ubuntu user for NVM install..."
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash
&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;. "&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;$HOME/.nvm/nvm.sh"
nvm install 22
nvm use 22
nvm alias default 22
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; Node.js installed. Verifying versions..."
node -v
nvm current
npm -v
EOF
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; Finished NVM and Node.js installation."

# --- Install dependencies and setup PM2 in video-to-mp3-app ---
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; Setting up application in /home/ubuntu/app/video-to-mp3-app..."
sudo -i -u ubuntu bash &amp;lt;&amp;lt; EOF
&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;. "&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;$HOME/.nvm/nvm.sh"
cd /home/ubuntu/app/video-to-mp3-app
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; Running npm install..."
npm install
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; npm install finished."
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; Installing PM2 globally..."
npm install pm2 -g
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; Starting server.js with PM2..."
pm2 start server.js --name video-converter
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; PM2 process started."
EOF
echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; Application setup finished."

echo "&amp;gt;&amp;gt;&amp;gt;&amp;gt; UserData script finished successfully."
`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Create the EC2 instance&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webapp-instance&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;instanceType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;instanceType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ami&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ami&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;a&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&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;vpcSecurityGroupIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;instanceSg&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;subnetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;subnetIds&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;ids&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="c1"&gt;// Use the first default subnet&lt;/span&gt;
    &lt;span class="na"&gt;keyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;keyPairName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Assign your key pair for SSH access&lt;/span&gt;
    &lt;span class="na"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Run the setup script on launch&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video-to-audio-converter&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="c1"&gt;// --- Application Load Balancer (ALB) ---&lt;/span&gt;

&lt;span class="c1"&gt;// Create the ALB, Target Group, and Listener&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LoadBalancer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webapp-lb&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;internal&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;loadBalancerType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;securityGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;albSg&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;subnets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;subnetIds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Assign the ALB to the default subnets&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video-to-audio-converter-alb&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;targetGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TargetGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webapp-tg&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;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;targetType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;instance&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;vpcId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vpcId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;healthCheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Basic health check for the root path&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP&lt;/span&gt;&lt;span class="dl"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;200-399&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;healthyThreshold&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;unhealthyThreshold&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="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;video--audio-converter-tg&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;targetGroupAttachment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TargetGroupAttachment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webapp-tg-attachment&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;targetGroupArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targetGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;targetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;instance&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;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3001&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;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Listener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;webapp-listener&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;loadBalancerArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;alb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HTTP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;defaultActions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;forward&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;targetGroupArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targetGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// --- Outputs ---&lt;/span&gt;
&lt;span class="c1"&gt;// Public DNS name of the ALB so we can access the app&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;albUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;alb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dnsName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Instance ID for reference&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;instanceId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;instance&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="c1"&gt;// Public IP of the instance for SSH access&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;instancePublicIp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicIp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what it does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configuration (&lt;code&gt;pulumi.Config&lt;/code&gt;):&lt;/strong&gt; Reads &lt;code&gt;appRepoUrl&lt;/code&gt; and &lt;code&gt;keyPairName&lt;/code&gt; from a config file. Sets &lt;code&gt;t2.micro&lt;/code&gt; as the default instance type.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Networking:&lt;/strong&gt; Uses the default AWS VPC and its subnets for simplicity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ALB Security Group (&lt;code&gt;alb-sg&lt;/code&gt;):&lt;/strong&gt; Controls traffic to the Load Balancer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;EC2 Security Group (&lt;code&gt;webapp-instance-sg&lt;/code&gt;):&lt;/strong&gt; Secures the EC2 instance, restricting inbound traffic to specific sources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;EC2 Instance (&lt;code&gt;aws.ec2.Instance&lt;/code&gt;):&lt;/strong&gt; Runs on the latest Ubuntu 22.04 LTS AMI with a setup script executed on boot.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UserData Script:&lt;/strong&gt; Automates app setup by installing tools, cloning the repo, and starting the application.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Application Load Balancer (&lt;code&gt;webapp-lb&lt;/code&gt;):&lt;/strong&gt; Public ALB that receives incoming traffic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Target Group (&lt;code&gt;webapp-tg&lt;/code&gt;):&lt;/strong&gt; Directs traffic to the EC2 instance with a health check.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Listener (&lt;code&gt;webapp-listener&lt;/code&gt;):&lt;/strong&gt; Connects the ALB to the target group.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Outputs:&lt;/strong&gt; Provides the ALB DNS name, instance ID, and instance public IP for access.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;PS:&lt;/strong&gt; It's completely okay to have doubts. A helpful tip, you can copy-paste &lt;code&gt;index.ts&lt;/code&gt; into &lt;strong&gt;Pulumi Copilot&lt;/strong&gt; to explore it further. It's available as a &lt;a href="https://marketplace.visualstudio.com/items?itemName=pulumi.pulumi-vscode-tools" rel="noopener noreferrer"&gt;Pulumi VS Code extension&lt;/a&gt; powered by their &lt;a href="https://www.pulumi.com/blog/pulumi-copilot-rest/" rel="noopener noreferrer"&gt;official REST API&lt;/a&gt;, and also on the &lt;a href="https://app.pulumi.com/" rel="noopener noreferrer"&gt;official Pulumi website&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8: Initialize Git &amp;amp; Push Our Project to GitHub
&lt;/h3&gt;

&lt;p&gt;Our EC2 instance (to be created by pulumi IaC) needs to download the application code. We also want to version control our infrastructure code. So, let's put everything into a single main Git repository.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Navigate to Parent Directory:&lt;/strong&gt; Ensure you are in the main &lt;code&gt;pulumi-video-converter&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# If you are inside pulumi-infra:&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Initialize Git:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create &lt;code&gt;.gitignore&lt;/code&gt;:&lt;/strong&gt; Create a &lt;code&gt;.gitignore&lt;/code&gt; file in the &lt;code&gt;pulumi-video-converter&lt;/code&gt; root to exclude unnecessary files:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Node modules
node_modules/
pulumi-infra/node_modules/
video-to-mp3-app/node_modules/

# Pulumi state and logs
.pulumi/
pulumi-infra/.pulumi/

# Application Uploads (local and server)
video-to-mp3-app/uploads/
uploads/

# OS generated files
.DS_Store
Thumbs.db

# Log files
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pm2.log
logs/

# Environment files
.env*
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add and Commit:&lt;/strong&gt; Stage all files and make your first commit.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initial commit: Add app code and Pulumi infra code"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Create GitHub Repository:&lt;/strong&gt; Go to GitHub and &lt;a href="https://github.com/new?name=pulumi-video-converter-deploy&amp;amp;description=My%20awesome%20repo&amp;amp;private=false" rel="noopener noreferrer"&gt;create a new public repository&lt;/a&gt; (e.g., &lt;code&gt;pulumi-video-converter-deploy&lt;/code&gt;). &lt;strong&gt;Do not&lt;/strong&gt; initialize it with a README, license, or .gitignore on GitHub.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Link Remote and Push:&lt;/strong&gt; Connect your local repository to the GitHub remote and push your code (replace the URL with &lt;em&gt;your&lt;/em&gt; new repository's URL):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote add origin https://github.com/your-username/pulumi-video-converter-deploy.git
git branch &lt;span class="nt"&gt;-M&lt;/span&gt; main
git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Why Git?&lt;/em&gt; It versions both your app and infrastructure code. The &lt;code&gt;userData&lt;/code&gt; script in &lt;code&gt;pulumi-infra/index.ts&lt;/code&gt;  relies on &lt;code&gt;git clone&lt;/code&gt; using the public URL you &lt;em&gt;configure in the next step&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Your current project structure should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pulumi-video-converter/  (Git repository root)
├── .git/
├── .gitignore
├── video-to-mp3-app/    # Application code (no .git folder here)
│   ├── node_modules/    # (Ignored by Git)
│   ├── public/
│   ├── uploads/         # (Ignored by Git, created by app)
│   ├── server.js
│   └── package.json
│   └── ...
└── pulumi-infra/        # Infrastructure code (Pulumi project)
    ├── node_modules/    # (Ignored by Git)
    ├── .pulumi/         # (Ignored by Git)
    ├── index.ts         # Pulumi IaC definition
    ├── package.json
    ├── Pulumi.yaml
    ├── Pulumi.dev.yaml  # Contains your config (repo URL, key name)
    └── tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 9: Configure Pulumi Stack (Repo URL &amp;amp; Key Pair)
&lt;/h3&gt;

&lt;p&gt;Now, tell Pulumi the &lt;strong&gt;URL of the repository&lt;/strong&gt; it needs to clone and the name of your &lt;strong&gt;EC2 key pair&lt;/strong&gt; (we'll create it later).&lt;/p&gt;

&lt;p&gt;Follow these steps:&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%2Fyii378xu4hlfah0ga5dr.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%2Fyii378xu4hlfah0ga5dr.gif" alt="Pulumi config app repo url"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Navigate to Infra Directory:&lt;/strong&gt; Make sure you are inside the &lt;code&gt;pulumi-infra&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;pulumi-infra
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Set GitHub Repository URL:&lt;/strong&gt; Use the &lt;strong&gt;HTTPS URL&lt;/strong&gt; of the main &lt;code&gt;pulumi-video-converter-deploy&lt;/code&gt; repository you created on Step 8.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Replace with YOUR project repository URL &amp;amp;&amp;amp; Execute it in your terminal&lt;/span&gt;
pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;appRepoUrl https://github.com/your-username/pulumi-video-converter-deploy.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;How to Create/Find Key Pair:&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A) Go to the &lt;a href="//console.aws.amazon.com/ec2/home"&gt;AWS EC2 Console&lt;/a&gt; in your target region (e.g., &lt;code&gt;ap-southeast-2&lt;/code&gt;). Navigate to &lt;em&gt;Network &amp;amp; Security&lt;/em&gt; -&amp;gt; &lt;em&gt;Key Pairs&lt;/em&gt;. &lt;/p&gt;

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

&lt;p&gt;B) &lt;em&gt;Click Create key pair&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;C) Give it a name (e.g., &lt;code&gt;video-converter-key&lt;/code&gt;) and choose &lt;code&gt;RSA&lt;/code&gt; type and &lt;code&gt;.pem&lt;/code&gt; format for SSH clients. Then click "Create key pair".  It will automatically download the &lt;code&gt;.pem&lt;/code&gt; file (e.g., &lt;code&gt;video-converter-key.pem&lt;/code&gt;).&lt;/p&gt;

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

&lt;p&gt;D) Configure Pulumi: Use the name you gave the key pair in AWS console (without &lt;code&gt;.pem&lt;/code&gt; extension).&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%2Fedyom9llpl0562xhg87q.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%2Fedyom9llpl0562xhg87q.gif" alt="COnfigure pulumi aws key name"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Replace 'video-converter-key' with the exact name from Console&lt;/span&gt;

pulumi config &lt;span class="nb"&gt;set &lt;/span&gt;keyPairName video-converter-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Why configure?&lt;/em&gt; This securely injects these values into your Pulumi program (&lt;code&gt;index.ts&lt;/code&gt;) where &lt;code&gt;config.require("appRepoUrl")&lt;/code&gt; and &lt;code&gt;config.get("keyPairName")&lt;/code&gt; are used, avoiding hardcoding sensitive or environment-specific details.&lt;/p&gt;

&lt;p&gt;Tip: After executing the commands, you can also double check the values you just set, by visting your &lt;code&gt;pulumi-infra/Pulumi.dev.yaml&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;aws:region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ap-southeast-2&lt;/span&gt;
  &lt;span class="na"&gt;pulumi-infra:appRepoUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/sojinsamuel/pulumi-video-converter-deploy.git&lt;/span&gt;
  &lt;span class="na"&gt;pulumi-infra:keyPairName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;video-converter-key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a snapshot of my &lt;code&gt;Pulumi.dev.yaml&lt;/code&gt; file, in your case, you'll have a different value for the &lt;code&gt;appRepoUrl&lt;/code&gt; and &lt;code&gt;keyPairName&lt;/code&gt;. if you come across any issues or mistyped your property values you can reset it by running the appropriate &lt;code&gt;pulumi config set "&amp;lt;command&amp;gt;"&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 10: Deploy to AWS!
&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%2F2m4zfmt7zfu6h29w536w.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%2F2m4zfmt7zfu6h29w536w.png" alt="pulumi up output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's time to bring your infrastructure to life! Run &lt;code&gt;pulumi up&lt;/code&gt; from within the &lt;code&gt;pulumi-infra&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Make sure you are in the pulumi-infra directory!&lt;/span&gt;
pulumi up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pulumi will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Show you a &lt;em&gt;preview&lt;/em&gt; of all the AWS resources it will create (VPC, Subnets, SGs, EC2, KeyPair association, ALB, Target Group, Listener, etc.).&lt;/li&gt;
&lt;li&gt; Ask for confirmation (&lt;code&gt;yes&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Review the plan carefully. If it looks right, type &lt;code&gt;yes&lt;/code&gt; and press Enter. Pulumi will provision the resources in AWS. This can take several minutes.&lt;/p&gt;

&lt;p&gt;Once finished, Pulumi will display the outputs, including the &lt;code&gt;albUrl&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Outputs:
    albUrl          : "webapp-lb-xxxxxxxxxxxxxxxxx.ap-southeast-2.elb.amazonaws.com"
    instanceId      : "i-xxxxxxxxxxxxxxxxx"
    instancePublicIp: "xx.xx.xx.xx"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 11: Verify Your Deployment
&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%2Fe4eo1duhe3sucil049wz.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%2Fe4eo1duhe3sucil049wz.gif" alt="Verify deployment made via pulumi to aws"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the &lt;code&gt;albUrl&lt;/code&gt; value from the Pulumi output and paste it into your web browser. You should see your Video Audio Extractor application!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test it:&lt;/strong&gt; Upload a small MP4 file, process it, download the MP3, and try generating the shareable link.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Troubleshooting Tips if your &lt;code&gt;albUrl&lt;/code&gt; fails to display the webapp in your browser:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invalid key pair:&lt;/strong&gt; If you see an invalid key pair error in your terminal after running &lt;code&gt;pulumi up&lt;/code&gt;, it likely means the key pair name in your AWS Console doesn’t match the one you set using &lt;code&gt;pulumi config&lt;/code&gt;. Double-check that both names are exactly the same.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One issue I ran into was that I had an old AWS access key and secret saved in my &lt;code&gt;~/.aws&lt;/code&gt; folder, which I had completely forgotten about. So when I ran &lt;code&gt;pulumi up&lt;/code&gt;, resources were getting created in my old AWS account. Pulumi then failed to use the key pair name I had created in my new account because that key pair didn’t exist in the old one.&lt;/p&gt;

&lt;p&gt;To avoid this, make sure you’ve set your AWS credentials and region correctly as explained in Step 6. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bad Gateway:&lt;/strong&gt; If you're getting a 502 Bad Gateway error after visiting the &lt;code&gt;albUrl&lt;/code&gt;, it usually means something went wrong during the execution of the &lt;code&gt;userData&lt;/code&gt; script in &lt;code&gt;pulumi-infra/index.ts&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To find the root cause, SSH into the EC2 instance and investigate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# &amp;lt;INSTANCE_PUBLIC_IP&amp;gt; is shown in your `pulumi up` output&lt;/span&gt;
&lt;span class="c"&gt;# Replace /path/to/your/key/yourkeyname.pem with the actual path to your key file&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /path/to/your/key/yourkeyname.pem ubuntu@&amp;lt;INSTANCE_PUBLIC_IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you're in, check the logs for script output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat /var/log/cloud-init-output.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You probably won’t even need to go through these troubleshooting steps, your &lt;code&gt;albUrl&lt;/code&gt; should display the application right away. I've already configured &lt;code&gt;pulumi-infra/index.ts&lt;/code&gt; after a lot of trial and error on my end.&lt;/p&gt;

&lt;p&gt;But if anything comes up, I'm just a message away on &lt;a href="https://linkedin.com/in/sojin-samuel" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;And there we have it! 🚀 We've successfully taken a standard Node.js video conversion application from a local setup to a fully deployed service on the AWS cloud, all orchestrated using Pulumi and TypeScript.&lt;/p&gt;

&lt;p&gt;We navigated the process of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Structuring our project with separate directories for application and infrastructure code.&lt;/li&gt;
&lt;li&gt;  Cloning the starter application code and preparing it for deployment.&lt;/li&gt;
&lt;li&gt;  Initializing a Pulumi project to define our cloud resources declaratively.&lt;/li&gt;
&lt;li&gt;  Writing TypeScript code to provision a VPC (default), EC2 instance, Application Load Balancer, Target Group, Listener, and necessary Security Groups.&lt;/li&gt;
&lt;li&gt;  Tackling the crucial EC2 &lt;code&gt;userData&lt;/code&gt; script, leveraging &lt;code&gt;nvm&lt;/code&gt; for reliable Node.js installation and &lt;code&gt;pm2&lt;/code&gt; to run our application.&lt;/li&gt;
&lt;li&gt;  Configuring Git and handling repository details for automated cloning during deployment.&lt;/li&gt;
&lt;li&gt;  Using &lt;code&gt;pulumi up&lt;/code&gt; to bring our infrastructure to life and iterating through troubleshooting steps (like credential issues, key pair mismatches, and &lt;code&gt;userData&lt;/code&gt; script errors).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This journey reflects a realistic workflow for deploying dynamic web applications. By defining our infrastructure as code, we've created a repeatable, version-controlled, and manageable deployment process – a huge step up from manual configurations!&lt;/p&gt;

&lt;h2&gt;
  
  
  💰 Cost Considerations
&lt;/h2&gt;

&lt;p&gt;Deploying resources to AWS incurs costs. While we've used the free-tier eligible &lt;code&gt;t2.micro&lt;/code&gt; instance type by default, be aware of the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;EC2 Instance:&lt;/strong&gt; &lt;code&gt;t2.micro&lt;/code&gt; instances are part of the AWS Free Tier (subject to hourly limits per month for the first 12 months of your AWS account). If you exceed these limits or use a different instance type, you will be charged per hour the instance is running.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Application Load Balancer (ALB):&lt;/strong&gt; ALBs are &lt;strong&gt;not&lt;/strong&gt; included in the standard Free Tier. You are charged for each hour an ALB is running and for Load Balancer Capacity Units (LCUs) consumed based on traffic metrics. This is likely the main cost driver for this deployment.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Data Transfer:&lt;/strong&gt; AWS charges for data transferred &lt;em&gt;out&lt;/em&gt; from EC2 and the ALB to the internet. Uploading videos incurs negligible ingress costs, but downloading the converted MP3s contributes to egress costs. Significant usage could lead to noticeable charges.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;EBS Volume:&lt;/strong&gt; The EC2 instance uses an EBS General Purpose SSD (gp2/gp3) volume for its root disk. There's a Free Tier allowance (e.g., 30 GB), but usage beyond that incurs costs.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Monitor Billing:&lt;/strong&gt; Keep an eye on your &lt;a href="https://console.aws.amazon.com/billing/home" rel="noopener noreferrer"&gt;AWS Billing Dashboard&lt;/a&gt; to understand costs.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Destroy When Done:&lt;/strong&gt; Most importantly, if you are only running this for the tutorial or testing, &lt;strong&gt;destroy the infrastructure&lt;/strong&gt; when you are finished to stop incurring charges:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run from the pulumi-infra directory&lt;/span&gt;
pulumi destroy
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Confirm &lt;code&gt;yes&lt;/code&gt; when prompted. This will terminate the EC2 instance, delete the ALB, and remove associated resources managed by Pulumi.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🤔 Using Pulumi: Lessons Learned &amp;amp; Copilot Assistance
&lt;/h2&gt;

&lt;p&gt;Deploying this application involved significantly more moving parts than a simple static website. Pulumi proved invaluable in managing this complexity, though the journey highlighted key cloud deployment concepts and debugging strategies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How Pulumi Benefited This Project:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Managing Interconnected Resources:&lt;/strong&gt; Defining the ALB, Target Group, Listener, EC2 Instance, and their associated Security Groups in TypeScript made their relationships explicit. Pulumi automatically understood dependencies (e.g., the instance needing the SG, the ALB needing the SG and subnets, the listener needing the ALB and TG) and created them in the correct order, preventing errors that are common with manual setups or less expressive tools. Referencing resource IDs directly in code (like &lt;code&gt;securityGroups: [albSg.id]&lt;/code&gt;) is much clearer than manually tracking ARNs or names.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Infrastructure as Code (IaC):&lt;/strong&gt; Having the &lt;em&gt;entire&lt;/em&gt; infrastructure defined in &lt;code&gt;index.ts&lt;/code&gt; means:

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Repeatability:&lt;/strong&gt; Anyone (including future you!) can run &lt;code&gt;pulumi up&lt;/code&gt; and get the same setup.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Version Control:&lt;/strong&gt; Our infrastructure definition lives in Git alongside the application code (in separate folders), allowing us to track changes, revert if needed, and collaborate.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Clarity:&lt;/strong&gt; The TypeScript code serves as documentation for the deployed resources.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Configuration Management:&lt;/strong&gt; Using &lt;code&gt;pulumi config set&lt;/code&gt; for the &lt;code&gt;appRepoUrl&lt;/code&gt; and &lt;code&gt;keyPairName&lt;/code&gt; was essential. It allowed us to inject environment-specific values into the deployment without hardcoding them in &lt;code&gt;index.ts&lt;/code&gt;, keeping the code clean and reusable for different stacks or users.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Iterative Development &amp;amp; Debugging &lt;code&gt;userData&lt;/code&gt;:&lt;/strong&gt; Bootstrapping EC2 instances with &lt;code&gt;userData&lt;/code&gt; is notoriously tricky. While Pulumi didn't write the script &lt;em&gt;for&lt;/em&gt; us, it made iterating much easier:

&lt;ul&gt;
&lt;li&gt;  We could define the multi-step installation process (NVM, Node, Git, clone, npm install, pm2) directly within our TypeScript code using &lt;code&gt;pulumi.interpolate&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  If the script failed (causing 502 errors), we could modify the &lt;code&gt;userData&lt;/code&gt; string in &lt;code&gt;index.ts&lt;/code&gt; and simply run &lt;code&gt;pulumi up&lt;/code&gt;. Pulumi would detect the change, replace the EC2 instance, and run the &lt;em&gt;new&lt;/em&gt; script, allowing for relatively quick test cycles compared to manually creating/terminating instances and running scripts.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;State Management &amp;amp; Recovery:&lt;/strong&gt; Although i hit issues due to initial credential mix-ups causing state inconsistencies, Pulumi's state management was ultimately helpful. Once the state became invalid (preventing &lt;code&gt;pulumi destroy&lt;/code&gt;), the &lt;code&gt;pulumi state delete &amp;lt;URN&amp;gt;&lt;/code&gt; command provided a necessary escape hatch to manually remove "ghost" resources from the state file, allowing us to reset and achieve a clean deployment with &lt;code&gt;pulumi up&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Leveraging Pulumi Copilot:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;During my troubleshooting phase, especially with &lt;code&gt;userData&lt;/code&gt; and configuration, Pulumi Copilot (&lt;a href="https://marketplace.visualstudio.com/items?itemName=pulumi.pulumi-vscode-tools" rel="noopener noreferrer"&gt;VS Code Extension&lt;/a&gt; / &lt;a href="https://app.pulumi.com/" rel="noopener noreferrer"&gt;Web&lt;/a&gt;) was very valuable to accelerate debugging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Initial &lt;code&gt;userData&lt;/code&gt; Structure:&lt;/strong&gt; When the first &lt;code&gt;apt-get install nodejs&lt;/code&gt; approach failed, a prompt like this helped explore alternatives:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Show me a Pulumi TypeScript example of EC2 userData to install Node.js v20 using nvm on Ubuntu 22.04 and run a simple Node app.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;(This provided the basic structure for using &lt;code&gt;sudo -i -u ubuntu bash &amp;lt;&amp;lt; EOF&lt;/code&gt; and installing/using nvm).&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configuration Issue:&lt;/strong&gt; The persistent &lt;code&gt;InvalidKeyPair.NotFound&lt;/code&gt; error, despite the key existing, led to asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Explain how pulumi config set stores values in Pulumi.yaml and how config.require() reads them in TypeScript compared to config.get().&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;(This clarified the project-name prefixing behavior and why &lt;code&gt;config.require&lt;/code&gt; was the correct choice).&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Debugging 502 Errors:&lt;/strong&gt; When the 502 errors occurred, general debugging prompts were useful:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Why would an AWS Application Load Balancer return a 502 Bad Gateway error when the EC2 instance seems to be running?&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;(Copilot suggested checking target group health, security groups allowing traffic on the app port from the ALB, and application logs on the instance – guiding the SSH troubleshooting steps).&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Specific Resource Syntax:&lt;/strong&gt; For refining the code:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;How do I correctly specify filters when using aws.ec2.getSubnets in Pulumi TypeScript?&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;(This helped fix the &lt;code&gt;vpcId&lt;/code&gt; vs &lt;code&gt;filters&lt;/code&gt; issue).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While Copilot didn't write the final, perfect script, it provided valuable code snippets, explanations of Pulumi concepts, and common debugging checklists that significantly reduced the time spent resolving issues encountered during this real-world deployment scenario.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Restrict SSH Access:&lt;/strong&gt; &lt;strong&gt;Immediately&lt;/strong&gt; update the Instance Security Group (&lt;code&gt;instance-sg&lt;/code&gt;) in &lt;code&gt;index.ts&lt;/code&gt; to allow SSH (port 22) &lt;strong&gt;only from your IP address&lt;/strong&gt; (&lt;code&gt;YOUR_IP/32&lt;/code&gt;) instead of &lt;code&gt;0.0.0.0/0&lt;/code&gt;. Run &lt;code&gt;pulumi up&lt;/code&gt; to apply.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTPS:&lt;/strong&gt; Secure your site using AWS Certificate Manager (ACM) and configure the ALB Listener for HTTPS (port 443).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Robust PM2 Startup:&lt;/strong&gt; Re-add and test &lt;code&gt;pm2 startup&lt;/code&gt; / &lt;code&gt;pm2 save&lt;/code&gt; (within the &lt;code&gt;ubuntu&lt;/code&gt; user context in &lt;code&gt;userData&lt;/code&gt;) if you need the application to restart automatically on server reboots.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;ffmpeg&lt;/code&gt; Dependency:&lt;/strong&gt; If deploying this to a different AMI, you might need to add &lt;code&gt;ffmpeg&lt;/code&gt; back to the &lt;code&gt;apt-get install&lt;/code&gt; line in &lt;code&gt;userData&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Static Assets:&lt;/strong&gt; Serve frontend files from S3/CloudFront.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Containerization:&lt;/strong&gt; Dockerize the app and deploy via ECS/Fargate managed by Pulumi.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CI/CD:&lt;/strong&gt; Automate deployments with GitHub Actions triggering &lt;code&gt;pulumi up&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cleanup:&lt;/strong&gt; When finished, run &lt;code&gt;pulumi destroy&lt;/code&gt; from the &lt;code&gt;pulumi-infra&lt;/code&gt; directory to remove all AWS resources and avoid costs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This journey, including the bumps along the way, is typical of real-world cloud deployments. By using Infrastructure as Code with Pulumi, we made the process manageable, repeatable, and ultimately successful!&lt;/p&gt;

&lt;p&gt;Feel free to &lt;a href="https://www.linkedin.com/in/sojin-samuel/" rel="noopener noreferrer"&gt;reach out on linkedin&lt;/a&gt; if you have questions or feedback!&lt;/p&gt;

&lt;p&gt;Happy coding champs 👋&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>pulumichallenge</category>
      <category>webdev</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Pulumi is the first ever IaC, i've ever tried + Pulumi Copilot is the perfect cherry on top to make the development much easier. Thanks a lot Team Pulumi</title>
      <dc:creator>Sojin Samuel</dc:creator>
      <pubDate>Fri, 04 Apr 2025 22:04:28 +0000</pubDate>
      <link>https://dev.to/sojinsamuel/pulumi-is-the-first-ever-iac-ive-ever-tried-pulumi-copilot-is-the-perfect-cherry-on-top-to-make-58cd</link>
      <guid>https://dev.to/sojinsamuel/pulumi-is-the-first-ever-iac-ive-ever-tried-pulumi-copilot-is-the-perfect-cherry-on-top-to-make-58cd</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/sojinsamuel/fast-track-your-static-site-deploying-hugo-with-pulumi-on-aws-s3-56l6" class="crayons-story__hidden-navigation-link"&gt;Fast-Track Your Static Site: Deploying Hugo with Pulumi on AWS S3&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/sojinsamuel" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F872759%2F0efe743b-bc0d-4970-8a40-1144077c7be0.jpg" alt="sojinsamuel profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/sojinsamuel" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Sojin Samuel
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Sojin Samuel
                
              
              &lt;div id="story-author-preview-content-2379625" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/sojinsamuel" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F872759%2F0efe743b-bc0d-4970-8a40-1144077c7be0.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Sojin Samuel&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/sojinsamuel/fast-track-your-static-site-deploying-hugo-with-pulumi-on-aws-s3-56l6" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 4 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/sojinsamuel/fast-track-your-static-site-deploying-hugo-with-pulumi-on-aws-s3-56l6" id="article-link-2379625"&gt;
          Fast-Track Your Static Site: Deploying Hugo with Pulumi on AWS S3
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/pulumichallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;pulumichallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cloud"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cloud&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/sojinsamuel/fast-track-your-static-site-deploying-hugo-with-pulumi-on-aws-s3-56l6" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;7&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/sojinsamuel/fast-track-your-static-site-deploying-hugo-with-pulumi-on-aws-s3-56l6#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              2&lt;span class="hidden s:inline"&gt; comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            8 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>devchallenge</category>
      <category>pulumichallenge</category>
      <category>webdev</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Fast-Track Your Static Site: Deploying Hugo with Pulumi on AWS S3</title>
      <dc:creator>Sojin Samuel</dc:creator>
      <pubDate>Fri, 04 Apr 2025 21:49:46 +0000</pubDate>
      <link>https://dev.to/sojinsamuel/fast-track-your-static-site-deploying-hugo-with-pulumi-on-aws-s3-56l6</link>
      <guid>https://dev.to/sojinsamuel/fast-track-your-static-site-deploying-hugo-with-pulumi-on-aws-s3-56l6</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;: Fast Static Website Deployment&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;In this tutorial, I'll guide you through deploying a static website using Hugo and Pulumi on AWS. Hugo is a fast and flexible static site generator, and Pulumi is an infrastructure-as-code tool that lets you define cloud resources using familiar programming languages like TypeScript. We'll set up a Hugo site, configure Pulumi to deploy it to an AWS S3 bucket, and make the site publicly accessible as a static website.&lt;/p&gt;

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

&lt;p&gt;You can access the &lt;a href="http://site-bucket-9170bd3.s3-website-ap-southeast-2.amazonaws.com/" rel="noopener noreferrer"&gt;deployed static hugo website here&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;The complete code for this project, including a detailed README, is available in my GitHub repository: &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/sojinsamuel" rel="noopener noreferrer"&gt;
        sojinsamuel
      &lt;/a&gt; / &lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws" rel="noopener noreferrer"&gt;
        pulumi-hugo-aws
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A fast static website deployment using Hugo and Pulumi on AWS S3, with a step-by-step tutorial for the Pulumi Deploy and Document Challenge.
    &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;Hugo and Pulumi Static Website Deployment on AWS S3&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This project demonstrates how to deploy a static website using &lt;a href="https://gohugo.io/" rel="nofollow noopener noreferrer"&gt;Hugo&lt;/a&gt; and &lt;a href="https://www.pulumi.com/" rel="nofollow noopener noreferrer"&gt;Pulumi&lt;/a&gt; on AWS S3. Hugo is a fast static site generator, and Pulumi is an infrastructure-as-code tool that allows you to define cloud resources using TypeScript. The site is deployed to an S3 bucket configured as a static website, with public access enabled for viewing.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of Contents&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#live-demo" rel="noopener noreferrer"&gt;Live Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#project-overview" rel="noopener noreferrer"&gt;Project Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#prerequisites" rel="noopener noreferrer"&gt;Prerequisites&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#setup-instructions" rel="noopener noreferrer"&gt;Setup Instructions&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#step-1-install-pulumi-cli" rel="noopener noreferrer"&gt;Step 1: Install Pulumi CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#step-2-set-up-your-project-directory" rel="noopener noreferrer"&gt;Step 2: Set Up Your Project Directory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#step-3-create-a-hugo-site" rel="noopener noreferrer"&gt;Step 3: Create a Hugo Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#step-4-create-a-pulumi-project" rel="noopener noreferrer"&gt;Step 4: Create a Pulumi Project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#step-5-configure-aws-credentials" rel="noopener noreferrer"&gt;Step 5: Configure AWS Credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#step-6-install-dependencies" rel="noopener noreferrer"&gt;Step 6: Install Dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#step-7-write-pulumi-code" rel="noopener noreferrer"&gt;Step 7: Write Pulumi Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#step-8-build-the-hugo-site" rel="noopener noreferrer"&gt;Step 8: Build the Hugo Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#step-9-deploy-with-pulumi" rel="noopener noreferrer"&gt;Step 9: Deploy with Pulumi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#step-10-verify-the-deployment" rel="noopener noreferrer"&gt;Step 10: Verify the Deployment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#project-structure" rel="noopener noreferrer"&gt;Project Structure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#troubleshooting" rel="noopener noreferrer"&gt;Troubleshooting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#next-steps" rel="noopener noreferrer"&gt;Next Steps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sojinsamuel/pulumi-hugo-aws#contributing" rel="noopener noreferrer"&gt;Contributing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Live Demo&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;You can view the deployed site here: &lt;a href="http://site-bucket-9170bd3.s3-website-ap-southeast-2.amazonaws.com/" rel="nofollow noopener noreferrer"&gt;http://site-bucket-657f8f1.s3-website-ap-southeast-2.amazonaws.com&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Project Overview&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;This project…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/sojinsamuel/pulumi-hugo-aws" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


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

&lt;p&gt;In this section, I’ll walk you through the entire process of setting up the project, configuring Pulumi, and deploying the static website to AWS S3. Along the way, I’ll highlight the challenges I faced and the solutions I found to ensure a smooth deployment.&lt;/p&gt;

&lt;p&gt;Before we begin, ensure you have the following installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://signin.aws.amazon.com/signup?request_type=register" rel="noopener noreferrer"&gt;An AWS account&lt;/a&gt; with an IAM user that has permissions for S3 operations (&lt;code&gt;s3:PutObject&lt;/code&gt;, &lt;code&gt;s3:GetObject&lt;/code&gt;, &lt;code&gt;s3:PutBucketPolicy&lt;/code&gt;, &lt;code&gt;s3:PutPublicAccessBlock&lt;/code&gt;, &lt;code&gt;s3:GetBucketPublicAccessBlock&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pulumi.com/docs/iac/download-install/" rel="noopener noreferrer"&gt;Pulumi CLI installed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/en/download" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; (version 16 or higher recommended; I used v20.12.2).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gohugo.io/installation/" rel="noopener noreferrer"&gt;Hugo installed&lt;/a&gt; installed (I used v0.128.0).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1: Install Pulumi CLI
&lt;/h3&gt;

&lt;p&gt;Install the Pulumi CLI by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://get.pulumi.com | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command downloads and installs the Pulumi CLI, which you’ll use to manage your cloud infrastructure.&lt;/p&gt;

&lt;p&gt;Verify the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fj3z6hg61vd07qsiyhzhx.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%2Fj3z6hg61vd07qsiyhzhx.gif" alt="Pulumi CLI installation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Set Up Your Project Directory
&lt;/h3&gt;

&lt;p&gt;Create a new directory for your project and navigate into it:&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="nb"&gt;mkdir &lt;/span&gt;pulumi-hugo-aws &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;pulumi-hugo-aws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This directory will hold all the files for your Hugo site and Pulumi project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Create a Hugo Site
&lt;/h3&gt;

&lt;p&gt;Initialize a new Hugo site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hugo new site mysite &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;mysite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a new Hugo site in the &lt;code&gt;mysite&lt;/code&gt; directory with the basic structure for your static site.&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%2Flkqyyjp0detb4w8gddoy.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%2Flkqyyjp0detb4w8gddoy.gif" alt="hugo initialization"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add a Theme:&lt;/strong&gt;&lt;br&gt;
Hugo sites need a theme to render content. We'll use the Ananke theme for this tutorial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
git submodule add https://github.com/budparr/gohugo-theme-ananke.git themes/ananke
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'theme = "ananke"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; hugo.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Create a Sample Post:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add a sample post to ensure there’s content to display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hugo new content/posts/my-first-post.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit the post to add some content and ensure it's published:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano content/posts/my-first-post.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the file to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;+++&lt;/span&gt;
&lt;span class="s"&gt;title = 'My First Post'&lt;/span&gt;
&lt;span class="s"&gt;date = 2025-04-04T23:41:34+05:30&lt;/span&gt;
&lt;span class="s"&gt;draft = &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="s"&gt;+++&lt;/span&gt;

&lt;span class="s"&gt;This is my first post on Hugo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Ft341587eknhp8qgyopcy.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%2Ft341587eknhp8qgyopcy.gif" alt="Hugo content post generation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save and exit (&lt;code&gt;Ctrl+O&lt;/code&gt;, &lt;code&gt;Enter&lt;/code&gt;, &lt;code&gt;Ctrl+X&lt;/code&gt; in &lt;code&gt;nano&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customize the Homepage:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By default, the Ananke theme's homepage doesn't list posts. Let's customize it to show recent posts:&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%2Fdnj2ek9uora88p858wh6.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%2Fdnj2ek9uora88p858wh6.gif" alt="hugo layout add"&gt;&lt;/a&gt;&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="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; layouts
nano layouts/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Add the following content:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;{{ define "main" }}
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{{ .Site.Title }}&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Welcome to my Hugo site!&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Recent Posts&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
      {{ range first 5 (where .Site.RegularPages "Section" "posts") }}
        &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ .RelPermalink }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ .Title }}&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;small&amp;gt;&lt;/span&gt;{{ .Date.Format "January 2, 2006" }}&lt;span class="nt"&gt;&amp;lt;/small&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
      {{ end }}
    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
{{ end }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and exit (&lt;code&gt;Ctrl+O&lt;/code&gt;, &lt;code&gt;Enter&lt;/code&gt;, &lt;code&gt;Ctrl+X&lt;/code&gt; in &lt;code&gt;nano&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;This template displays the site title and a list of up to 5 recent posts from the &lt;code&gt;posts&lt;/code&gt; section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Create a Pulumi Project
&lt;/h3&gt;

&lt;p&gt;Initialize a new Pulumi project in the &lt;code&gt;mysite&lt;/code&gt; directory:&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%2F7jo4fwfqwgguuwkmyeue.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%2F7jo4fwfqwgguuwkmyeue.gif" alt="pulumi initialization"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi new aws-typescript &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--force&lt;/code&gt; is needed because the directory isn’t empty (Hugo already created files).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Follow the prompts to set up your Pulumi project. This command initializes a new Pulumi project using the AWS TypeScript template, which provides a starting point for deploying AWS resources using TypeScript.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;P.S:&lt;/strong&gt; If you want to learn why &lt;code&gt;pulumi new&lt;/code&gt; expects us to always start from an empty directory, &lt;a href="https://github.com/pulumi/pulumi/issues/2671" rel="noopener noreferrer"&gt;check out this GitHub discussion&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Configure AWS Credentials
&lt;/h3&gt;

&lt;p&gt;Make sure your &lt;a href="https://www.pulumi.com/registry/packages/aws/installation-configuration/" rel="noopener noreferrer"&gt;AWS credentials are configured&lt;/a&gt;. You can do this by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command sets up your AWS credentials, which are necessary for Pulumi to interact with AWS services. You will be prompted to enter your AWS &lt;code&gt;Access Key ID&lt;/code&gt;, &lt;code&gt;Secret Access Key&lt;/code&gt;, and default &lt;code&gt;region&lt;/code&gt;. Your IAM user must have permissions for S3 operations (see Prerequisites).&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Install Dependencies
&lt;/h3&gt;

&lt;p&gt;Install the necessary Pulumi packages and mime-types for handling file types:&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; @pulumi/aws @pulumi/pulumi mime-types @types/mime-types &lt;span class="nt"&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;@pulumi/aws&lt;/code&gt; and &lt;code&gt;@pulumi/pulumi&lt;/code&gt; are required for AWS resource management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;mime-types&lt;/code&gt; helps set correct MIME types for uploaded files.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 7: Write Pulumi Code
&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%2Fl28s7c7dc5cs51okih5i.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%2Fl28s7c7dc5cs51okih5i.gif" alt="add pulumi IaC code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Replace the default &lt;code&gt;index.ts&lt;/code&gt; with the following code to set up an S3 bucket, configure it as a website, and upload the Hugo site:&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="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;aws&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;@pulumi/aws&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;pulumi&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;@pulumi/pulumi&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fs&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;fs&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;path&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;path&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="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;mime&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;mime-types&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Create an S3 bucket&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;siteBucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;site-bucket&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="c1"&gt;// Configure the bucket as a website&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;website&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BucketWebsiteConfigurationV2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;website&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;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;siteBucket&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;indexDocument&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index.html&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="c1"&gt;// Configure public access block to allow public access&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;publicAccessBlock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BucketPublicAccessBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public-access-block&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;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;siteBucket&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;blockPublicAcls&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;blockPublicPolicy&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;ignorePublicAcls&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;restrictPublicBuckets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Add a bucket policy to allow public read access&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucketPolicy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BucketPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bucketPolicy&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;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;siteBucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;siteBucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bucketName&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2012-10-17&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Allow&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Principal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;Action&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;s3:GetObject&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`arn:aws:s3:::&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bucketName&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dependsOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;publicAccessBlock&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Function to recursively upload files from a directory&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;uploadDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;withFileTypes&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="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;dirent&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="nx"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&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="nx"&gt;dirPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dirent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;uploadDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;relativePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./public&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BucketObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;relativePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;siteBucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FileAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&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;application/octet-stream&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;relativePath&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;dependsOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;publicAccessBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;website&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;bucketPolicy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Upload all files from the Hugo 'public' directory&lt;/span&gt;
&lt;span class="nf"&gt;uploadDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./public&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;siteBucket&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Export the website URL&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;bucketEndpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pulumi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interpolate&lt;/span&gt;&lt;span class="s2"&gt;`http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;website&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;websiteEndpoint&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and exit (&lt;code&gt;Ctrl+O&lt;/code&gt;, &lt;code&gt;Enter&lt;/code&gt;, &lt;code&gt;Ctrl+X&lt;/code&gt; in &lt;code&gt;nano&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This code:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Creates an S3 bucket and configures it as a website with &lt;code&gt;index.html&lt;/code&gt; as the default page.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Disables S3 Block Public Access settings to allow public access.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Applies a bucket policy to make all objects publicly readable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Recursively uploads all files from the &lt;code&gt;public/&lt;/code&gt; directory, preserving the directory structure and setting correct MIME types.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 8: Build Hugo Site
&lt;/h3&gt;

&lt;p&gt;Generate the static files for your Hugo site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hugo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates the &lt;code&gt;public/&lt;/code&gt; directory with your site’s static files, including &lt;code&gt;index.html&lt;/code&gt; and the post at &lt;code&gt;posts/my-first-post/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9: Deploy with Pulumi
&lt;/h3&gt;

&lt;p&gt;Deploy the site to AWS:&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%2Fws4ay1dfs874pid3vxx3.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%2Fws4ay1dfs874pid3vxx3.gif" alt="pulumi deploy to aws"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pulumi up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Pulumi will preview the changes, showing the resources to be created (S3 bucket, website configuration, bucket policy, and objects).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select &lt;code&gt;yes&lt;/code&gt; to deploy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once complete, the output will include &lt;code&gt;bucketEndpoint&lt;/code&gt; (e.g., &lt;code&gt;http://site-bucket-9170bd3.s3-website-ap-southeast-2.amazonaws.com&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 10: Verify the Deployment
&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%2Fzjc2bqd0x2b551rs9m0x.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%2Fzjc2bqd0x2b551rs9m0x.gif" alt="hugo deployed website using pulumi IaC"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open the &lt;code&gt;bucketEndpoint&lt;/code&gt; URL in a browser. You should see your Hugo site with the Ananke theme, displaying the site title ("My New Hugo Site") and a "Recent Posts" section listing "My First Post" with a link to the full post.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project Structure (Key Files Only)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pulumi-hugo-aws/
└── mysite/
    ├── archetypes/         # Hugo archetypes for new content
    ├── content/            # Hugo content files (e.g., posts/)
    │   └── posts/
    │       └── my-first-post.md
    ├── layouts/            # Custom Hugo templates
    │   └── index.html
    ├── public/             # Generated static files
    ├── themes/             # Hugo themes (e.g., ananke/)
    ├── hugo.toml           # Hugo configuration
    ├── index.ts            # Pulumi deployment script
    ├── package.json        # Node.js dependencies
    ├── Pulumi.yaml         # Pulumi project configuration
    └── tsconfig.json       # TypeScript configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;In this tutorial, we successfully deployed a static website using Hugo and Pulumi on AWS S3. We covered setting up a Hugo site with a theme, customizing the homepage to display posts, configuring Pulumi to deploy to S3, and ensuring the site is publicly accessible.&lt;/p&gt;

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

&lt;p&gt;Pulumi made this project much easier to manage. I was able to define and deploy AWS infrastructure using TypeScript, which I already use daily. That let me stay in one workflow without needing to switch between tools or learn a new configuration language. Here's how Pulumi helped me get the Hugo site up and running on S3:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;TypeScript for Infrastructure&lt;/strong&gt;: Writing infrastructure in TypeScript meant fewer surprises. I could catch issues like incorrect resource properties before deployment and stay consistent with the rest of the project. Compared to YAML or JSON-based tools, the development process felt more natural.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS Setup with Less Friction&lt;/strong&gt;: Pulumi’s AWS provider (&lt;code&gt;@pulumi/aws&lt;/code&gt;) gave me a direct way to create what I needed in S3. Buckets, static website configs, policies. it all came together in a clear and code-first way. I didn't have to spend extra time wrestling with CloudFormation or the AWS console.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Helpful Copilot Tooling&lt;/strong&gt;: The &lt;a href="https://www.pulumi.com/product/copilot/" rel="noopener noreferrer"&gt;Pulumi Copilot&lt;/a&gt; helped me troubleshoot faster. When I ran into public access issues, it explained how the &lt;code&gt;BucketPublicAccessBlock&lt;/code&gt; resource worked and gave an example I could use. It also clarified some TypeScript syntax issues and pointed me to the right docs for deeper reading on S3 configs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Smarter State Management&lt;/strong&gt;: Pulumi's state tracking meant that when I updated the Hugo &lt;code&gt;public/&lt;/code&gt; directory, only the changed files were uploaded. That saved time and made updates feel more reliable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There were a few moments where I had to figure things out, but the tooling and docs helped me get through them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Nested Folder Uploads&lt;/strong&gt;: Hugo’s output includes nested directories and my first attempt didn’t handle those correctly. I ended up writing a recursive function on &lt;code&gt;index.ts&lt;/code&gt; to upload all the files while keeping the structure. The &lt;a href="https://www.pulumi.com/docs/concepts/assets-archives/" rel="noopener noreferrer"&gt;assets and archives guide&lt;/a&gt; helped with that.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Public Access Errors&lt;/strong&gt;: At first, S3’s Block Public Access settings blocked my custom bucket policy, which caused &lt;code&gt;AccessDenied&lt;/code&gt; errors. Pulumi Copilot helped me fix this by showing how to use &lt;code&gt;BucketPublicAccessBlock&lt;/code&gt;. The &lt;a href="https://www.pulumi.com/registry/packages/aws/how-to-guides/s3-website/" rel="noopener noreferrer"&gt;example policy&lt;/a&gt; in the docs also gave me a solid reference.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Homepage Didn't Show Posts&lt;/strong&gt;: Hugo’s default setup doesn’t display posts on the homepage, so I had to create a custom &lt;code&gt;layouts/index.html&lt;/code&gt;. This part was more of a Hugo issue, but once I rebuilt the site, Pulumi picked up the changes and deployed them without extra work on my end.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the end, Pulumi let me stay close to my code and avoid bouncing between services and dashboards. Hugo handled the content and site structure, and Pulumi made sure it reached the web with minimal overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Now that your Hugo site is live, consider these next steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add More Content&lt;/strong&gt;: Create additional &lt;a href="http://gohugo.io/commands/" rel="noopener noreferrer"&gt;posts or pages in Hugo&lt;/a&gt; to expand your site.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enable HTTPS&lt;/strong&gt;: Set up AWS CloudFront with an &lt;a href="https://repost.aws/knowledge-center/cloudfront-https-requests-s3" rel="noopener noreferrer"&gt;SSL certificate to serve your site over HTTPS&lt;/a&gt; (S3 websites are HTTP-only by default).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CI/CD Integration&lt;/strong&gt;: Automate deployments with &lt;a href="https://www.pulumi.com/automation/" rel="noopener noreferrer"&gt;Pulumi's automation API&lt;/a&gt; and a CI/CD pipeline (e.g., GitHub Actions).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Recursive File Uploads&lt;/strong&gt;: Always account for nested directories when uploading static sites to S3. Use a recursive function to preserve the directory structure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;IAM Permissions&lt;/strong&gt;: Verify your AWS IAM user has the necessary permissions for S3 operations to avoid AccessDenied errors.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By following this tutorial, you can leverage Pulumi and Hugo to deploy scalable, efficient static websites with ease.&lt;/p&gt;

&lt;p&gt;Feel free to &lt;a href="https://www.linkedin.com/in/sojin-samuel/" rel="noopener noreferrer"&gt;reach out on linkedin&lt;/a&gt; if you have any questions. &lt;/p&gt;

&lt;p&gt;Happy coding champs 👋&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>pulumichallenge</category>
      <category>webdev</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Win Up to $1200 in the InfinityAI Halloween Challenge 🎃</title>
      <dc:creator>Sojin Samuel</dc:creator>
      <pubDate>Wed, 30 Oct 2024 00:19:23 +0000</pubDate>
      <link>https://dev.to/sojinsamuel/win-up-to-1200-in-the-infinityai-halloween-challenge-2hgi</link>
      <guid>https://dev.to/sojinsamuel/win-up-to-1200-in-the-infinityai-halloween-challenge-2hgi</guid>
      <description>&lt;p&gt;If you’re interested in exploring AI video creation or sharing your own Halloween-inspired ideas, the &lt;strong&gt;InfinityAI Halloween Challenge&lt;/strong&gt; is for you. This month-long event invites creators to make Halloween or horror-themed videos using InfinityAI-generated characters, with up to &lt;strong&gt;$1200 in prizes&lt;/strong&gt; available.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the InfinityAI Halloween Challenge?
&lt;/h2&gt;

&lt;p&gt;The InfinityAI Halloween Challenge brings together AI video creators to showcase Halloween and horror-themed videos. Each week in October features new themes, and now, in this final week, the theme is &lt;strong&gt;“Free for All.”&lt;/strong&gt; This means any Halloween or horror-inspired video that includes a notable amount of InfinityAI characters is welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  October Challenge Details
&lt;/h2&gt;

&lt;p&gt;This is the last chance to enter and compete for prizes in the &lt;strong&gt;October Challenge&lt;/strong&gt;. The best video of the month will win &lt;strong&gt;$500&lt;/strong&gt;, and the second place will receive &lt;strong&gt;$100&lt;/strong&gt;. With the prize pool for this month totaling &lt;strong&gt;$1200&lt;/strong&gt;, there are many opportunities to win!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Enter? Prizes and Community
&lt;/h2&gt;

&lt;p&gt;Here’s the complete prize breakdown:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;InfinityAI Team Prizes:&lt;/strong&gt; $50 for 1st place and $25 for 2nd place. The prize pool increases by $100 for every 10 unique participants (up to $500).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community Choice Prize:&lt;/strong&gt; $50 goes to the finalist who receives the most votes in a community poll.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Participating connects you with an active community of AI creators, giving you the chance to share your work, learn from others, and see different ways AI can be used in video.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Participate
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a Video:&lt;/strong&gt; Use InfinityAI to generate characters and scenes for a Halloween or horror concept.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Submit Your Entry:&lt;/strong&gt; Make sure your video includes a significant amount of InfinityAI-generated content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deadline:&lt;/strong&gt; Entries for the final week are due by &lt;strong&gt;Monday, November 4th&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Ready to Join?
&lt;/h2&gt;

&lt;p&gt;Check out all the details and submit your entry here: &lt;a href="https://studio.infinity.ai/competitions" rel="noopener noreferrer"&gt;InfinityAI Halloween Challenge Link&lt;/a&gt;&lt;/p&gt;

</description>
      <category>halloween</category>
      <category>ai</category>
    </item>
    <item>
      <title>Networking cant be easier than this</title>
      <dc:creator>Sojin Samuel</dc:creator>
      <pubDate>Sun, 23 Jun 2024 11:34:20 +0000</pubDate>
      <link>https://dev.to/sojinsamuel/networking-cant-be-easier-than-this-4fdm</link>
      <guid>https://dev.to/sojinsamuel/networking-cant-be-easier-than-this-4fdm</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/twilio"&gt;Twilio Challenge &lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;An AI agent were you can pass in an email or a linkedin profile url to get candidate details and share it with your partner, cofounder via:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.twilio.com/docs/voice/make-calls"&gt;Twilio Call&lt;/a&gt;, &lt;a href="https://www.twilio.com/en-us/messaging/channels/sms"&gt;SMS&lt;/a&gt; or &lt;a href="https://app.sendgrid.com/"&gt;Email&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Agent user can also pass in the phone number of a potential candidate and get his/her details regarding the availability of the phone, which was made with the help of &lt;a href="https://help.twilio.com/articles/15515453000859"&gt;Twilio Phone Lookup service&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This Agent also has the power of displaying real time notifications for example when an email is sent and the recipient opens it, the AI Agent dashboard notifies the user, when they have opened the mail.&lt;/p&gt;

&lt;p&gt;eg:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Hey&lt;/span&gt; &lt;span class="nx"&gt;sojinsamue2001&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;gmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt; &lt;span class="nx"&gt;just&lt;/span&gt; &lt;span class="nx"&gt;opened&lt;/span&gt; &lt;span class="nx"&gt;their&lt;/span&gt; &lt;span class="nx"&gt;mail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And importantly the user data: like the signup, login events, message events back and forth between the user and AI, page visits etc are tracked with the help of &lt;a href="https://segment.com/"&gt;Twilio segment&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://reversecontact.vercel.app"&gt;Project Link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Demo video:&lt;br&gt;
 &lt;iframe width="710" height="399" src="https://www.youtube.com/embed/O8hQnAyhUB4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Twilio and AI
&lt;/h2&gt;

&lt;p&gt;I have used &lt;a href="https://sdk.vercel.ai/docs/introduction"&gt;Vercel AI SDK&lt;/a&gt; with gpt4o to render react components which then interacts with Twilio SDK to send messages via sms, email and as a call. Combining GPT function calling and executing actions using natural language.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/sojinsamuel/reverse-contact"&gt;Source code on github&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Qualify for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Twilio Times Two: utilized Twilio Segment for studying customer data, Twilio Phone lookup endpoint to get candidate phone details, Twilio SMS and Programmable voice for notifying colleagues or candidates, Twilio Sendgrid for sending candidate details in a dynamically generated email template using gpt.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Impactful Innovators: Best for Networking and get details by just submitting an email address to the Agent and if that email is associated with a linkedin account then get their details and send it to your team for further verification or checks before hiring. And the data thats returned from the Reverse Contact lookup can also be passed on to email or programmable voice by mentioning what you want to share in natural language, which helps you to manage more than one channel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Entertaining Endeavors: Getting realtime information from just an email id and passing that data into others channel with the help of webhooks to notify the recipient without missing important notifications like hiring, interview, etc&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>twiliochallenge</category>
      <category>ai</category>
      <category>twilio</category>
    </item>
    <item>
      <title>TweetGagger Plugin: getLikes, getRetweets, getReplies</title>
      <dc:creator>Sojin Samuel</dc:creator>
      <pubDate>Sat, 27 Apr 2024 18:35:01 +0000</pubDate>
      <link>https://dev.to/sojinsamuel/tweetgagger-plugin-getlikes-getretweets-getreplies-544f</link>
      <guid>https://dev.to/sojinsamuel/tweetgagger-plugin-getlikes-getretweets-getreplies-544f</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/devteam/join-us-for-the-coze-ai-bot-challenge-3000-in-prizes-4dp"&gt;Coze AI Bot Challenge&lt;/a&gt;: Trailblazer.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;TweetGagger needs an id associated with a TWeet URL to get all the details which includes details of the liking users, retweeting users, replies being made to that particular tweet.&lt;/p&gt;

&lt;p&gt;I made this plugin for my &lt;a href="https://www.coze.com/s/ZmFqHEBe6/"&gt;GiveAway Picker bot&lt;/a&gt;, where it needed to check actions mentioned on a giveaway contest hosted on Twitter where Authors asking users to Like, Retweet and Comment something specific, then a random user(s) get shortlisted for winning prices.&lt;/p&gt;

&lt;p&gt;But this isn't just a plugin that usefull for my bot, you can use this to get engagement metrics of a tweet, check for Spamming users who are making irrelevant comments etc&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flitmrjou1dgvxtwli1ed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flitmrjou1dgvxtwli1ed.png" alt="TweetGagger Plugin" width="800" height="374"&gt;&lt;/a&gt;&lt;br&gt;
Find the &lt;a href="https://www.coze.com/store/plugin/7361890366561353736?from=explore_card"&gt;TweetGagger Plugin&lt;/a&gt; from Store.&lt;/p&gt;

&lt;p&gt;getRetweet function execution response from GiveAwayPicker bot.&lt;/p&gt;

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

&lt;p&gt;Each functions are triggered only when an action associated with it is mentioned in the Tweet.&lt;/p&gt;

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

&lt;p&gt;Submitted via Coz Studio, at first it did seem a little scary. But finally I did make it work out.&lt;/p&gt;

&lt;p&gt;Thanking Coze for hosting such a wonderful hackathon and this is my last submission to this challenge.&lt;/p&gt;

&lt;p&gt;Best of luck to all the participants.&lt;/p&gt;

</description>
      <category>cozechallenge</category>
      <category>devechallenge</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Tweet Media Extractor Plugin</title>
      <dc:creator>Sojin Samuel</dc:creator>
      <pubDate>Sat, 27 Apr 2024 11:24:46 +0000</pubDate>
      <link>https://dev.to/sojinsamuel/tweet-media-extractor-plugin-4a35</link>
      <guid>https://dev.to/sojinsamuel/tweet-media-extractor-plugin-4a35</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/devteam/join-us-for-the-coze-ai-bot-challenge-3000-in-prizes-4dp"&gt;Coze AI Bot Challenge&lt;/a&gt;: Trailblazer.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;There are already amazing Twitter related plugins available on Coze Plugins store that can be used with Tweets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F63a1t0fx83p9mtq5nx47.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F63a1t0fx83p9mtq5nx47.png" alt="Plugin store coze"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What I needed was a plugin that could take the Media comprised in a Tweet, could be an image or video (Which I needed in different resolutions)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fh720nomt31jozjdfmzrl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fh720nomt31jozjdfmzrl.png" alt="Tweet media extractor plugin"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I built one to use on my &lt;a href="https://dev.to/sojinsamuel/how-tweetmediamanager-transforms-tweet-urls-into-valuable-resources-99n"&gt;TweetMediaManager bot&lt;/a&gt;, which is an integral part for the bots functionality.&lt;/p&gt;

&lt;p&gt;When a user submits a tweet or post URL:&lt;br&gt;
&lt;code&gt;https://twitter.com/&amp;lt;username&amp;gt;/status/&amp;lt;id&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It has a unique id associated with it. which is the input parameter being used in TweetMediaExtractor to make a LookUp search via Twitter API v2.&lt;/p&gt;

&lt;p&gt;If the tweet does comprise media the JSON response would contain an includes field.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Assuming&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Tweet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;contained&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;video&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"includes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"media_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"77777777"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"video"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"variants"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"bit_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;632000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"content_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"video/mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://video.twimg.com/ext_tw_video/12345678/pu/vid/avc1/320x568/4024rVUaMBVYHT_b.mp4?tag=12"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"bit_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;950000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"content_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"video/mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://video.twimg.com/ext_tw_video/12345678/pu/vid/avc1/480x852/5JGUFqyletKVFUuF.mp4?tag=12"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"bit_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2176000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"content_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"video/mp4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://video.twimg.com/ext_tw_video/12345678/pu/vid/avc1/720x1280/E9zgV0hONDsfOwrq.mp4?tag=12"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"content_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/x-mpegURL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://video.twimg.com/ext_tw_video/12345678/pu/pl/ORE8nOl29XDVW9kz.m3u8?tag=12&amp;amp;container=cmaf"&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"users"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


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

&lt;/div&gt;

&lt;p&gt;Even though we could download an image by going to devtools via inspect on chrome, using the same method for a video will only give you a blob URL. which isn't what we want. Many people do share awesome infographical or gif representation of AWS architectural patterns which is what my motivation behind building this custom plugin.&lt;/p&gt;

&lt;p&gt;At the time of building this, I wasn't familiar with the Coze Studio and was honestly confused like what are tools? where do they even come in action. where would I safely store my Twitter API Keys, what does Tool name mean.&lt;/p&gt;

&lt;p&gt;Even though there is already options from Coze to bind existing external service APIs. I still needed the Studio option because I needed to implement oauth 1.0 authentication method for the Twitter API.&lt;/p&gt;

&lt;p&gt;So my second option was to create a REST API via AWS API gateway and make a resource path &lt;code&gt;/getmedia&lt;/code&gt;, which then executes a lambda function and the id needed was passed via query param by registering it as an Existing service (used the API Gateway REST API endpoint).&lt;/p&gt;

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

&lt;p&gt;You can check out the &lt;a href="https://www.coze.com/store/plugin/7362301488493969413?from=explore_card" rel="noopener noreferrer"&gt;TweetMediaExtractor plugin from Coze plugin store&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F4gdgupj2j8i5oxcu37qu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F4gdgupj2j8i5oxcu37qu.png" alt="Tweet Media Extractor Jason response"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the JSON response we saw earlier, the plugin usage in action can be tested via my &lt;a href="https://www.coze.com/s/ZmFqHVFVQ/" rel="noopener noreferrer"&gt;TweetMediaManager bot&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Even though I moved forward with using API Gateway instead of Coze Studio, it was still a great experience to learn about this AWS service and made my development process much easier.&lt;/p&gt;

&lt;p&gt;Still, I wasn't gonna let go of using Coze Studio that easily!&lt;/p&gt;

&lt;p&gt;It is indeed a learning process, So i created another plugin called TweetGagger (yeah another plugin powered by Twitter API) which I made it work from the Studio itself instead of using API gateway.&lt;/p&gt;

&lt;p&gt;Read: TweetGagger Plugin Submission&lt;/p&gt;

</description>
      <category>cozechallenge</category>
      <category>devechallenge</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Randomly Picks a Giveaway Winner on Twitter (Oops X)</title>
      <dc:creator>Sojin Samuel</dc:creator>
      <pubDate>Fri, 26 Apr 2024 22:56:15 +0000</pubDate>
      <link>https://dev.to/sojinsamuel/randomly-picks-a-giveaway-winner-on-twitter-oops-x-10m9</link>
      <guid>https://dev.to/sojinsamuel/randomly-picks-a-giveaway-winner-on-twitter-oops-x-10m9</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/devteam/join-us-for-the-coze-ai-bot-challenge-3000-in-prizes-4dp7"&gt;Coze AI Bot Challenge&lt;/a&gt;: Bot Innovator.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Giving Away Prices is becoming a thing on X nowadays. From books to Crypto coins are just some. But sometimes a lot of people participate in it and the people who are hosting this giveaway might be having a hard time to choose that winner.&lt;/p&gt;

&lt;p&gt;They have to make sure they choose a person without making any personal or emotional choices and strictly abide the rules.&lt;/p&gt;

&lt;p&gt;The Team behind the contest could hire someone to do the job. But what if the Author has a bot that will choose for him/her randomly.&lt;/p&gt;

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

&lt;p&gt;Some conversation snapshots:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fjn4a21o3b7geqix4hlqq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fjn4a21o3b7geqix4hlqq.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is what &lt;a href="https://www.coze.com/s/ZmFqHyCbo/" rel="noopener noreferrer"&gt;GiveAwayPicker&lt;/a&gt; bot does.&lt;/p&gt;

&lt;p&gt;Wait, but what if the Author wants to choose more than one person in ranks. Its not always going to be just a single winner&lt;/p&gt;

&lt;p&gt;Already covered 😉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fenwakm80bdq8bxvefdh0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fenwakm80bdq8bxvefdh0.png" alt="Giveaway picker bit examples"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.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%2F2o7s214bknmjy3u6lffb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F2o7s214bknmjy3u6lffb.png" alt="Giveaway picker bot plugins"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;TweetLinkParser check the URL and verifies if the tweet is actually talking about a GiveAway Contest + What are the actions mentioned in the Tweet for example: Like, Retweet, replies.&lt;/p&gt;

&lt;p&gt;Based on the actions provided appropriate function are executed with the help of TweetGagger Plugin.&lt;/p&gt;

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

// Prompt
# Character
You're an efficient Twitter Giveaway Analyzer. You are proficient at checking, verifying and analyzing "Giveaway Tweets" based on the author's instructions. 

## Skills
### Skill 1: Tweet Analysis
- Use getTweet to analyze the content of the provided tweet.
- Determine if it's a giveaway tweet and understand what the author asks the participants to do in order to win the giveaway.

### Skill 2: Function Execution
- Use the getReplies, getRetweet, and getlikes functions as needed, according to the tweet content.
- If additional actions besides liking, retweeting, and replying are required and are impossible to perform; explain the limitations to the user and ask whether the available options are acceptable.

### Skill 3: Determining the Winner
- Identify those who've completed all required actions.
- Select only one user (unless the author has explicitly mentioned he may be selecting more than one) at random and inform the giver of the winner's Twitter handle allowing them to distribute the prize.

## Constraints:
- If the giveaway tweet contains atleast one action that you dont support then let the user know i cant support that particular action and explicitly ask to the user if the user wants you check any or all of the action you support
- Process only tweets that indicate clearly what actions the participants must take to be qualified as potential winners of the giveaway.
- Interpret "repost" in a tweet as a request to "retweet" and use getRetweet accordingly.
- If "comment" or "reply" is mentioned but without specifying what needs to be commented or replied, still activate the getReplies function.
- Reveal only the winner's details, twitter handle link of the winner, no other information or details of the participants should be displayed.
- Remember to use only the necessary functions, based on the giveaway criteria described in the tweet.
- If a tweet's criteria for the giveaway can't be met due to the lack of necessary functions, inform the user about the issue and offer the alternative of executing functions that are available.
- In the output make sure you only include the one single winner (unless the author has explicitly asked he needs more than one winner) Twitter handle along with the link to check their profile and also adding the congratulatory message


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

&lt;/div&gt;

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

&lt;p&gt;&lt;a href="https://dev.to/sojinsamuel/how-tweetmediamanager-transforms-tweet-urls-into-valuable-resources-99n"&gt;Based on the first bot I built for the contest&lt;/a&gt; which downloads media from Tweets, that was hard at the time because I was still learning and in the path of being comfortable to a new development environment. Give-away Picker bot was an idea I already had in mind, but there wasnt any plugins that could check Likes, Retweets, Replies details. So I need to build and deploy TweetGagger from Coze Studio.&lt;/p&gt;

</description>
      <category>cozechallenge</category>
      <category>devchallenge</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>This Bot Downloads Media from any Tweet and Set Reminders for Future reference</title>
      <dc:creator>Sojin Samuel</dc:creator>
      <pubDate>Wed, 24 Apr 2024 10:14:47 +0000</pubDate>
      <link>https://dev.to/sojinsamuel/how-tweetmediamanager-transforms-tweet-urls-into-valuable-resources-99n</link>
      <guid>https://dev.to/sojinsamuel/how-tweetmediamanager-transforms-tweet-urls-into-valuable-resources-99n</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/devteam/join-us-for-the-coze-ai-bot-challenge-3000-in-prizes-4dp7"&gt;Coze AI Bot Challenge&lt;/a&gt;: Bot Innovator.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;I have been using Twitter since 2019 and have seen many valuable resources shared by people, ranging from AWS architectural patterns to SEO tips, web design principles, etc.&lt;/p&gt;

&lt;p&gt;The highlight often is the media associated with it, which could be a representation of a diagram or a video explanation.&lt;/p&gt;

&lt;p&gt;I often do what we've always done: bookmark it for future reference. The problem is, most of us never come back. In my case, after a couple of months, I may have even forgotten that I saved something like this.&lt;/p&gt;

&lt;p&gt;I even wondered if it would have been great if the bookmarking feature also had some kind of reminder mechanism that I could schedule, like 'remind me next month,' etc.&lt;/p&gt;

&lt;p&gt;Sadly, Twitter (Now X) hasn't rolled it out yet.&lt;/p&gt;

&lt;p&gt;This is where &lt;em&gt;TweetMediaManager&lt;/em&gt; comes to the rescue.&lt;/p&gt;

&lt;p&gt;You can send a Tweet URL that looks something like this to the bot:&lt;br&gt;
&lt;code&gt;https://twitter.com/&amp;lt;username&amp;gt;/status/&amp;lt;id&amp;gt;&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;And later, this is what happens:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpy2ofuj07yy0ltznqf4m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpy2ofuj07yy0ltznqf4m.png" alt="coze ai hackathon tweet media manager" width="627" height="1014"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But that's not just it. You could also schedule a reminder of a particular Tweet for a specific time, whether it be tomorrow, next week, or next month. That's up to you.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvlz5o5l8e7mb2btpmfi5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvlz5o5l8e7mb2btpmfi5.png" alt="coze ai hackathon tweet media manager" width="697" height="911"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll see a live demo of both of these examples in a sec 😉&lt;/p&gt;

&lt;p&gt;I'm preparing for the AWS Solutions Architect exam, and this is going to help me a lot because there are some awesome developers on X who are sharing really valuable tips, including diagrams, gif representations of some internal processes, etc., from time to time.&lt;/p&gt;

&lt;p&gt;Even if you're not preparing for any exams, you could still use this for many different purposes. The possibilities are endless and unique to each of you.&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1782169943540400148-638" src="https://platform.twitter.com/embed/Tweet.html?id=1782169943540400148"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1782169943540400148-638');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1782169943540400148&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;In my case, I used this bot to download the video where Jack Dorsey presents Twitter back in 2006. This simple video motivates me to work harder and keep going.&lt;/p&gt;

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

&lt;p&gt;You can check out &lt;a href="https://www.coze.com/s/ZmFqugeoX/"&gt;TweetMediaManager bot from Coze store&lt;/a&gt;. It's also available on Discord and Telegram.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/QsJNsmswrqk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/aQUjzAuVJ6M"&gt;
&lt;/iframe&gt;
&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// prompt
# Character
You're an expert in digital content extraction specializing in obtaining multimedia materials from Twitter. Sharp, meticulous, and excellent in preserving original content quality, you shine at analysing tweets and retrieving embedded media files.

## Skills
### Skill 1: Media Extraction from Tweet URLs
- Analyze the structure and syntax of provided Tweet URLs, focusing on image or video content.
- Extract these files, maintaining their original form and quality.

### Skill 2: Secure Media Delivery 
- Following secured media extraction, use reliable and safe data transmission means to forward the obtained media files to the user.
- Ensure the unaltered, original condition receipt of these files by the user.

## Constraints
- Use the Tweet_Processor tool to extract the id from a valid Twitter URL, obtain media URLs and share these along with URL specifics like resolution based on bitrate, and the text content of the tweet.
- Stick to URLs originating from Twitter exclusively. Other platforms are not within operational limits.
- Implement ‘url_storage' to verify redundancy of the Tweet URL and save novel URLs along with their respective media URL, if any.
- Clean up the shared tweet text; remove hashtags, mentions, promotions, keep it simple, and emphasize it in bold. Also include the original tweet link and any media content.
- Guide the user towards the correct URL format if provided URLs are inconsistent with the examples:

A) https://twitter.com/&amp;lt;username&amp;gt;/status/&amp;lt;id&amp;gt;

B) https://x.com/&amp;lt;username&amp;gt;/status/&amp;lt;id&amp;gt;

- Emphasize the importance of non-commercial, personal, and ethical use of retrieved media content.
- When the media type is a video just provide the response as the text, which includes: Download URL with all obtainable resolutions you have for example define the resolution as a label based on the bit rate , Tweet text etc and do not use card data attachment on the media type video.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Learning something new is never easy for anybody and it did took some time to get the whole Coze workflow for building an AI chatbot.&lt;/p&gt;

&lt;p&gt;For this particular bot, none of the currently available plugins were able to bring my idea to life. So i have to build one which in this case i had to use the &lt;a href="https://developer.twitter.com/en/docs/twitter-api/tweets/lookup/api-reference/get-tweets"&gt;Twitter API v2 Tweet Lookup endpoint&lt;/a&gt; with some extra parameters and expansions to get the media (both image and video) from a tweet.&lt;/p&gt;

&lt;p&gt;Unfortunately i was having a hard time to figure out how to create this custom plugin and configure it to make a call to Twitter API v2 since it required Oauth 1.0 authentication setup with Consumer key, Consumer Secret, Access Token and Access token secrets. Bearer token aren't used anymore for most cases&lt;/p&gt;

&lt;p&gt;So i had to either drop this idea or move on with another one. which i couldn't. so i created a RestAPI on AWS APIGateway and executed a lambda function containing the programmatic logic to make a call to Twitter with Oauth 1.0 and returned a JSON comprising of the media details when a particular endpoint is accessed.&lt;/p&gt;

&lt;p&gt;After a couple of hours of publishing &lt;em&gt;TweetMediaManager&lt;/em&gt; i saw the users count increasing, which did made me worry and happy at the same time because to access the Tweet Lookup endpoint (to get media) i needed a Paid API plan from twitter so i choose the $100/mon plan for my use case. But the main problem was the rate limiting issue (429 error). could arise when a specific endpoint is consumed a lot more than it should on a time period (its dfferent for different API plans) of users are having a conversation at the same time or either i could go for a $5K/mon plan which does gives a lot more freedom, which i can't for this hackathon. if Coze could help, I'm definitely gonna make this plugin production ready so you can all utilize it for any number of bots.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.youtube.com/@CozeHQ"&gt;videos on CozeHQ official Youtube channel&lt;/a&gt; was very helpful for starters for building, utilizing different skills, publishing it to various channels like Telegram etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/@CozeHQ/streams"&gt;Most importantly the Live Stream on April 22 was really awesome&lt;/a&gt;, &lt;a class="mentioned-user" href="https://dev.to/joshalphonse"&gt;@joshalphonse&lt;/a&gt; and &lt;a class="mentioned-user" href="https://dev.to/zarazhangrui"&gt;@zarazhangrui&lt;/a&gt; helped me to clear lot of my doubts in that available time frame. We all built a Travel Assistant bot together that day and it was such a great experience for me.&lt;/p&gt;

&lt;p&gt;Lastly, best of luck to all contenders of Coze AI hackathon, feel free to share your projects on X with a mention to @SamuelSojin.&lt;/p&gt;

&lt;p&gt;I'm psyched to test all of your amazing hackathon submissions.&lt;/p&gt;

&lt;p&gt;Peace ✌️&lt;/p&gt;

</description>
      <category>cozechallenge</category>
      <category>devchallenge</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>How To Create a WhatsApp Chatbot in 2025 With a Custom Knowledge Base</title>
      <dc:creator>Sojin Samuel</dc:creator>
      <pubDate>Thu, 25 Jan 2024 22:10:51 +0000</pubDate>
      <link>https://dev.to/sojinsamuel/integrating-chatgpt-into-my-whatsapp-with-a-custom-knowledge-base-2pl2</link>
      <guid>https://dev.to/sojinsamuel/integrating-chatgpt-into-my-whatsapp-with-a-custom-knowledge-base-2pl2</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this post, we assume that you have already installed Node v18 on your machine and has some experience with Supabase, and PostgreSQL. Additionally, make sure you have a free Twilio account set up (if you don't have one yet). We will be developing a WhatsApp chatbot using the &lt;a href="https://platform.openai.com/docs/guides/text-generation/chat-completions-api" rel="noopener noreferrer"&gt;OpenAI Chat Completions&lt;/a&gt; API endpoint.&lt;/p&gt;

&lt;p&gt;Users can ask questions, and the &lt;a href="https://sojinsamuel.hashnode.dev/how-to-make-chat-bot" rel="noopener noreferrer"&gt;chatbot will generate responses&lt;/a&gt; based on the information provided through a vector database. It will also ensure to refrain from answering questions when they are asked out of context from its knowledge base, opting instead to reply with a polite sorry message.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up your Local Environment
&lt;/h2&gt;

&lt;p&gt;First, set up your Node.js application. Make sure you understand how to create an Express server before you proceed.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install dependencies&lt;/strong&gt;: In your project terminal start by executing the following command to install the necessary packages:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm i @supabase/supabase-js twilio express dotenv openai langchain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After executing this command, you'll find a node project dependency tree in the root of your project, complete with a package.json file containing the specified dependencies.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Add Environment variables&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Create a .env file with the following contents. which will be used to authenticate REST API requests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TWILIO_ACCOUNT_SID= &amp;lt;your twilio account sid&amp;gt;
TWILIO_AUTH_TOKEN= &amp;lt;your twilio auth token&amp;gt;

OPENAI_API_KEY= &amp;lt;your open ai key&amp;gt;

SUPABASE_URL= &amp;lt;your supabase url&amp;gt;
SUPABASE_API_KEY= &amp;lt;your supabase api key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a breakdown on how to get these credentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Obtaining Twilio Credentials:&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Log in to your &lt;a href="https://www.twilio.com/login" rel="noopener noreferrer"&gt;Twilio account&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Navigate to the &lt;a href="https://console.twilio.com/" rel="noopener noreferrer"&gt;Twilio Auth Token&lt;/a&gt; page.&lt;/li&gt;
&lt;li&gt;Copy and paste the credentials into the &lt;code&gt;.env&lt;/code&gt; file.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Obtaining OpenAI API Key:&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Log in to your &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;OpenAI account&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Head to the &lt;a href="https://platform.openai.com/api-keys" rel="noopener noreferrer"&gt;OpenAI dashboard&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Copy and paste the OpenAI API key into the &lt;code&gt;.env&lt;/code&gt; file.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Obtaining Supabase Credentials:&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Create a &lt;a href="https://database.new/" rel="noopener noreferrer"&gt;Supabase project&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Go to the &lt;a href="https://supabase.com/dashboard/project/_/settings/api" rel="noopener noreferrer"&gt;API settings page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Copy and paste the Supabase API URL and API key into the &lt;code&gt;.env&lt;/code&gt; file.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Configuring OpenAI and Supabase
&lt;/h2&gt;

&lt;p&gt;In the root of your project, create a file called &lt;code&gt;utils.js&lt;/code&gt;. This is where we'll configure our OpenAI and Supabase client.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// loads .env variables&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OpenAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openai&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@supabase/supabase-js&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;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&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;SUPABASE_URL&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;SUPABASE_API_KEY&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;openai&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;OpenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&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;OPENAI_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&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;supabase&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code consists of 2 core steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Supabase Initialization&lt;/strong&gt;: Sets up a connection to Supabase.This initialized supabase client allows interaction with the Supabase database in the application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI Instance&lt;/strong&gt;: Initializes the OpenAI API client., which is crucial for making requests to the OpenAI API, enabling the application to perform NLP tasks.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Create another file on your project root called &lt;code&gt;server.js&lt;/code&gt;.&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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&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="p"&gt;{&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;supabase&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./utils&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlencoded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;extended&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="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MessagingResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twilio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;twiml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MessagingResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/incoming&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="nx"&gt;message&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;body&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;twiml&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;MessagingResponse&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// We'll create a function to reply for the incoming message here later&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aiReply&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;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;twiml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aiReply&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;twiml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Express server listening on port 3000&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;The above code consists of 4 core steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Importing Twilio helper package&lt;/strong&gt;: we are using the &lt;code&gt;MessagingResponse&lt;/code&gt; constructor to create an instance of a &lt;a href="https://www.twilio.com/docs/voice/twiml" rel="noopener noreferrer"&gt;TwiML (Twilio Markup Language) response&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Initializing Express App&lt;/strong&gt;: We're creating a web server using Express to listen for POST requests to &lt;code&gt;/incoming&lt;/code&gt; route. This route will act as our &lt;a href="https://www.twilio.com/docs/usage/webhooks/getting-started-twilio-webhooks" rel="noopener noreferrer"&gt;Twilio WhatsApp webhook&lt;/a&gt; to receive incoming messages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incoming Request&lt;/strong&gt;: The incoming request body has a property called &lt;code&gt;Body&lt;/code&gt;, which will contain the text message the user sent via WhatsApp to the Twilio server. This message will then be forwarded to our web server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhook Response&lt;/strong&gt;: Finally, our web server will respond with a reply generated using OpenAI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Writing the Reply Function
&lt;/h2&gt;

&lt;p&gt;Let's create the &lt;code&gt;reply&lt;/code&gt; function in the &lt;code&gt;server.js&lt;/code&gt; file so that we can transmit the incoming message to the &lt;strong&gt;OpenAI Chat Completions endpoint&lt;/strong&gt;.&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;chatMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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="s1"&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="s1"&gt;reply to the messages you get in 100 character&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;chatMessages&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="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="s1"&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="nx"&gt;msg&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;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;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chatMessages&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="s1"&gt;gpt-3.5-turbo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;frequency_penalty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&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;We call this function inside our web server, as you saw earlier. The &lt;code&gt;req.body.Body&lt;/code&gt; argument is passed to the reply function as the &lt;code&gt;msg&lt;/code&gt; parameter. Then, we respond to it using the &lt;strong&gt;OpenAI Chat Completions&lt;/strong&gt; endpoint.&lt;/p&gt;

&lt;p&gt;Now, our web server will look like:&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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&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="p"&gt;{&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;supabase&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./utils&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlencoded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;extended&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="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MessagingResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twilio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;twiml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MessagingResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/incoming&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="nx"&gt;message&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;body&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;twiml&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;MessagingResponse&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;aiReply&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;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;twiml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aiReply&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;twiml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Express server listening on port 3000&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;chatMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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="s1"&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="s1"&gt;reply to the messages you get in 100 characters&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;chatMessages&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="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="s1"&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="nx"&gt;msg&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;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;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chatMessages&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="s1"&gt;gpt-3.5-turbo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;frequency_penalty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&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;Now, return to your terminal and execute the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have configured everything correctly, as we mentioned. You will see this message printed on the console.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Express server listening on port 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But there is a problem. Your web server is currently running locally on your computer. As a result, Twilio will not be able to access our webhook endpoint.&lt;/p&gt;

&lt;p&gt;This is where we need to use &lt;strong&gt;Ngrok&lt;/strong&gt; to expose our local server to the internet. If you haven't used it before, &lt;a href="https://www.twilio.com/docs/usage/tutorials/how-to-set-up-your-node-js-and-express-development-environment" rel="noopener noreferrer"&gt;Twilio has already provided instructions on how to install and configure it on your machine&lt;/a&gt;. Once it's done (assuming the local server is still running), open another terminal in the same path and execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ngrok http 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will expose our server to the internet. From the terminal, you can copy the forwarding URL with HTTPS and paste it into &lt;a href="https://console.twilio.com/us1/develop/sms/try-it-out/whatsapp-learn" rel="noopener noreferrer"&gt;Twilio WhatsApp Sandbox settings&lt;/a&gt;. Don't forget to add the &lt;code&gt;/incoming&lt;/code&gt; route at the end and set the method to POST, then click Save.&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%2Fj8fb3vm6mhcex971bhzm.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%2Fj8fb3vm6mhcex971bhzm.png" alt="chatgpt in whatsapp" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, go back to the Twilio Whatsapp Sandbox tab and scan the QR code.&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%2Ftz9wnu2gnvt4tofd0938.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%2Ftz9wnu2gnvt4tofd0938.png" alt="whatsapp chatgpt" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From your WhatsApp, now you can chat with our AI just as you would normally with ChatGPT.&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%2Fj5n5wk2m6bse7a5j9o3r.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%2Fj5n5wk2m6bse7a5j9o3r.gif" alt="Twilio whatsapp chat demo" width="342" height="778"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Knowledge base
&lt;/h2&gt;

&lt;p&gt;A big challenge of working with &lt;a href="https://www.cloudflare.com/learning/ai/what-are-embeddings/" rel="noopener noreferrer"&gt;embeddings&lt;/a&gt; is that traditional relational databases like MySQL or PostgreSQL cannot handle the complexity and scale of all that data. Therefore, AI engineers need a specialized storage system to efficiently handle high-dimensional vectors, and that's where vector databases come in.&lt;/p&gt;

&lt;p&gt;For this project, we're going to use PostgreSQL. Wait a second, did we just say it's not possible with it? Yes, but we are going to give it some superpowers with the help of an &lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;extension called pgvector&lt;/a&gt;. Which is already available in Supabase Extension store.&lt;/p&gt;

&lt;p&gt;Now, let's create a table to store the vectors. Copy and paste the following code into your SQL editor, Which you can find it with in the &lt;code&gt;supabase.com/dashboard/project/&amp;lt;your-project-id&amp;gt;/sql/new&lt;/code&gt;. This editor allows you to write and run SQL queries and scripts on your database.&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="n"&gt;extension&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;movies&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;bigserial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&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="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;-- corresponds to the "text chunk"&lt;/span&gt;
  &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;vector&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="c1"&gt;-- 1536 works for OpenAI embeddings&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Function to find similar movies based on cosine distance with adjustable threshold and count.&lt;/span&gt;
&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="k"&gt;replace&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;match_movies&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;query_embedding&lt;/span&gt; &lt;span class="n"&gt;vector&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;match_threshold&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;match_count&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="k"&gt;table&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="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="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;similarity&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;language&lt;/span&gt; &lt;span class="k"&gt;sql&lt;/span&gt; &lt;span class="k"&gt;stable&lt;/span&gt;
&lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
  &lt;span class="k"&gt;select&lt;/span&gt;
    &lt;span class="n"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;movies&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;lt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;similarity&lt;/span&gt;
  &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;movies&lt;/span&gt;
  &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;movies&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;lt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query_embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;match_threshold&lt;/span&gt;
  &lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;movies&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;lt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query_embedding&lt;/span&gt;
  &lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="n"&gt;match_count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't need to feel overwhelmed by this SQL query. it's already available in the &lt;a href="https://supabase.com/docs/guides/ai/vector-columns" rel="noopener noreferrer"&gt;Supabase docs&lt;/a&gt;. We have simply optimized the naming for movies.&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%2Fwx5k63xmx6plsgg8d5ab.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%2Fwx5k63xmx6plsgg8d5ab.gif" alt="whatsapp chatgpt" width="822" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above code consists of 3 core steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Enabling pgvector&lt;/strong&gt;: Initially, we activate the pgvector extension for our PostgreSQL database to enable the use of the vector type for our embedding column.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Similarity Search&lt;/strong&gt;: The &lt;code&gt;match_movies&lt;/code&gt; function, which we will utilize later, checks the similarity of vector embeddings between the user query and the vector embeddings in the database.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cosine Similarity&lt;/strong&gt;: Recommended by &lt;a href="https://platform.openai.com/docs/guides/embeddings" rel="noopener noreferrer"&gt;OpenAI for their model text-embedding-ada-002&lt;/a&gt;. The model has 1536 dimensions for each text.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If everything runs successfully, you will see &lt;strong&gt;Success: No rows returned in the result.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, it's time to store some vector embeddings in the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Vector Embeddings
&lt;/h2&gt;

&lt;p&gt;Amazon recommends products, Google shows results based on your queries, YouTube, Netflix, and Spotify recommend your favorites. They don't understand the context between two titles or products, even if they mean the same thing. They truly get us with the help of AI.&lt;/p&gt;

&lt;p&gt;To be exact, with the help of embeddings. When an embedding is created for a word, the vectors preserve its original meaning and the relationship between other words and phrases.&lt;/p&gt;

&lt;p&gt;It is just a numerical snapshot of data. A word, sentence, or an entire document can be reduced to a vector. Now as developers, it might be easier to think of a vector as an array of floating-point numbers.&lt;/p&gt;

&lt;p&gt;Let's create a new file in the root called &lt;code&gt;data.js&lt;/code&gt;. This is where we are going to add the data for our chatbot to Supabase Vector DB.&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;// data.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;movies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Welcome to Redville: 2023 | 1h 30m | 4.8 rating | Genre: Crime, Drama, Mystery &amp;amp; thriller......`&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;supabase&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./utils&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RecursiveCharacterTextSplitter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;langchain/text_splitter&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createEmbedding&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./utils&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;splitDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&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;splitter&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;RecursiveCharacterTextSplitter&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;chunkSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;chunkOverlap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&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;output&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;splitter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDocuments&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;output&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="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;pageContent&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pageContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createEmbedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageContent&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;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;movies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;splitDocuments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code consists of 3 core steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge base&lt;/strong&gt;: We have generated some content for you, which includes movies where AI models like chatGPT have no prior knowledge. &lt;a href="https://github.com/sojinsamuel/whatsapp-chatbot/blob/master/movies.txt" rel="noopener noreferrer"&gt;You can find this content in this GitHub repository&lt;/a&gt;. Simply copy and assign it to the movies variable inside backticks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add Content&lt;/strong&gt;: To insert content and embeddings into Supabase, you can use the Supabase insert method. The data array is passed to the insert method, sending all the data to Supabase in a single batch. When adding the data, ensure that the data properties match the column names in the movies table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split Text into Chunks&lt;/strong&gt;: When creating embeddings from large text documents, it's beneficial to first break the text into smaller chunks. This ensures that the AI model can effectively capture and understand the context to providing more accurate results. That's why we are using the &lt;a href="https://js.langchain.com/docs/modules/data_connection/document_transformers/text_splitters/recursive_text_splitter" rel="noopener noreferrer"&gt;RecursiveCharacterTextSplitter&lt;/a&gt; from Langchain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And &lt;code&gt;createEmbedding&lt;/code&gt; is a new utility function in our &lt;code&gt;utils.js&lt;/code&gt;:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createEmbedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&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;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;embeddings&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="s1"&gt;text-embedding-ada-002&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;encoding_format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;float&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&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;supabase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createEmbedding&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how you call the OpenAI's embedding model &lt;code&gt;text-embedding-ada-002&lt;/code&gt;, that generates text embeddings.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Required Props&lt;/strong&gt;: The &lt;code&gt;model&lt;/code&gt; and &lt;code&gt;input&lt;/code&gt; are required properties in the request body. And if you want to include multiple inputs in a single request you can pass an array on strings.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Similarity Check&lt;/strong&gt;: This embedding model is well-trained to understand language. It can embed words and phrases with similar meanings into a similar vector space and the ones that are not alike into a different vector space.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, open your terminal and execute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node data.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will store our movie data in our vector database as text chunks, and each chunk will point to a vector embedding of 1536 dimensions.&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%2F1j9h8bjdihgt8zj7fw1v.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%2F1j9h8bjdihgt8zj7fw1v.gif" alt="whatsapp chatbot" width="1024" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to compare the embedding created from the text message you sent with the vectors in our database to find a similar match and retrieve the corresponding text.&lt;/p&gt;

&lt;p&gt;This is where our special function comes into play. We will be utilizing an algorithm to measure the similarity between two vectors, known as cosine similarity. Supabase, with the pg vector extension makes the comparison and processing of vectors easy and fast with the &lt;code&gt;match_movies&lt;/code&gt; SQL function we saw earlier.&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;movies&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;supabaseClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;match_movies&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;query_embedding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Embedding you want to compare&lt;/span&gt;
  &lt;span class="na"&gt;match_threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// min threshold (78%)&lt;/span&gt;
  &lt;span class="na"&gt;match_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// limit no of matches&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, let's make some changes to our server.js file:&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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&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="p"&gt;{&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;createEmbedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./utils&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;chatMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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="s1"&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="s2"&gt;`You are an enthusiastic movie expert who loves recommending movies to people. You will be given two pieces of information - some context about movies and a question. Your main job is to formulate a short answer to the question using the provided context. If you are unsure and cannot find the answer in the context, say, "Sorry, I don't know the answer." Please do not make up the answer.`&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;findNearestMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;movies&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;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;match_movies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;match_threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;match_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// No match returns []&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&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;embedding&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;createEmbedding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&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;movies&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;findNearestMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;embedding&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;movies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No match found. Please try again.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;chatMessages&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="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="s1"&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;`Context: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Question: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;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;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chatMessages&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="s1"&gt;gpt-3.5-turbo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;frequency_penalty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlencoded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;extended&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="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MessagingResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twilio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;twiml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MessagingResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/incoming&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="nx"&gt;message&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;body&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;twiml&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;MessagingResponse&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;aiReply&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;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;twiml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aiReply&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;twiml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Express server listening on port 3000&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;The above code consists of 2 core steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Execute match_movies function&lt;/strong&gt;: The function named findNearestMatch searches through our supabase database to locate the closest matching text chunk based on the provided embedding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add ChatGPT Wrapper&lt;/strong&gt;: We can achieve more dynamic and conversational response by sending the matched text to OpenAI’s chat completion endpoint and instructing the model to formulate a specific answer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, if you haven't stopped the server, restart it. Each time Ngrok will give you a new forwarding URL, so make sure you update the webhook URL in the WhatsApp sandbox settings with your new one. Don't forget to add the &lt;code&gt;/incoming&lt;/code&gt; route at the end.&lt;/p&gt;

&lt;p&gt;Now, let's have a chat with the final version of our WhatsApp chatbot:&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%2Ftnkd0k0urs50vu74uot0.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%2Ftnkd0k0urs50vu74uot0.gif" alt="whatsapp auto reply" width="1024" height="1024"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;This tutorial demonstrates the many possibilities of using OpenAI to automate various tasks. Instead of just having a textual conversation, we could also add a feature to input commands like &lt;code&gt;/img your prompt&lt;/code&gt; for image generation or a piece of text to convert it into speech. The possibilities are endless.&lt;/p&gt;

&lt;p&gt;What would you like to add next to this Whatsapp chatbot?&lt;/p&gt;

&lt;p&gt;If you have some crazy ideas in mind make sure to &lt;a href="https://www.linkedin.com/in/sojin-samuel/" rel="noopener noreferrer"&gt;reach out me on linkedin&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Peace ✌️&lt;/p&gt;

</description>
      <category>whatsapp</category>
      <category>javascript</category>
      <category>openai</category>
      <category>twilio</category>
    </item>
    <item>
      <title>A Twitter Bot for Personalized Replies to Your Followings and Targeted Keywords!</title>
      <dc:creator>Sojin Samuel</dc:creator>
      <pubDate>Mon, 05 Jun 2023 03:16:25 +0000</pubDate>
      <link>https://dev.to/sojinsamuel/a-twitter-bot-for-personalized-replies-to-your-followings-and-targeted-keywords-11b6</link>
      <guid>https://dev.to/sojinsamuel/a-twitter-bot-for-personalized-replies-to-your-followings-and-targeted-keywords-11b6</guid>
      <description>&lt;p&gt;Just built another Twitter bot for my client.&lt;/p&gt;

&lt;p&gt;This bot replies to tweets posted by the people client followed &amp;amp;&amp;amp; also searches for tweets containing specific keywords that comes under the client niche and replies to them. Reply were generated using gpt 3.5 turbo.&lt;/p&gt;

&lt;p&gt;Screen Recording of the working bot in practice (The one i sent to my client): &lt;/p&gt;

&lt;p&gt;&lt;a href="//drive.google.com/file/d/1C8XnzaSemB3TsUraJOLLUiwoua-Q7UvY/view"&gt;https://drive.google.com/file/d/1C8XnzaSemB3TsUraJOLLUiwoua-Q7UvY/view&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to automate your tasks too, &lt;a href="mailto:sojinsamuel2001@gmail.com?subject=Sojin%20I%20want%20to%20automate%20my%20business&amp;amp;body=Yes%20I%26%2339%3Bm%20curious%20how%20it%20works%20if%20we%20can%20build%20out%20more%20automation%20for%20my%20twitter%20growth"&gt;I am open to new Twitter bot ideas&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>twitter</category>
      <category>node</category>
      <category>javascript</category>
      <category>openai</category>
    </item>
    <item>
      <title>Stacking Context Mastery: Understanding and Troubleshooting Z-Index in CSS</title>
      <dc:creator>Sojin Samuel</dc:creator>
      <pubDate>Sat, 28 Jan 2023 12:46:57 +0000</pubDate>
      <link>https://dev.to/sojinsamuel/stacking-context-mastery-understanding-and-troubleshooting-z-index-in-css-2l5d</link>
      <guid>https://dev.to/sojinsamuel/stacking-context-mastery-understanding-and-troubleshooting-z-index-in-css-2l5d</guid>
      <description>&lt;p&gt;Are you tired of struggling with stacking context and z-index in your CSS? &lt;/p&gt;

&lt;p&gt;As a web developer, understanding these concepts is crucial for achieving the desired visual hierarchy in your designs.&lt;/p&gt;

&lt;p&gt;Unfortunately, many tutorials and articles fail to cover this topic in depth, leaving many developers feeling lost and frustrated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1zjx2g10rk22pu6ya17p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1zjx2g10rk22pu6ya17p.jpg" alt="z-index" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am excited to announce that I have created a comprehensive guide on stacking context and z-index, today on gumroad.&lt;/p&gt;

&lt;p&gt;This will completely change the way you approach the visual hierarchy of your website.&lt;/p&gt;

&lt;p&gt;Through extensive research and practical examples, I have made it easy for you to grasp these essential concepts. The guide includes code pen examples and interactive challenges to ensure retention of the material.&lt;/p&gt;

&lt;p&gt;Unlike many tutorials, my guide delves into the fundamentals of stacking context before diving into the intricacies of z-index. This approach ensures a solid foundation for understanding and troubleshooting these crucial elements of CSS. The guide covers the following topics:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This guide is free for 7 days and i want you to try it out and i am sure that it will be worth while&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://sojinsamuel.gumroad.com/l/stacking-context-in-css"&gt;Check it out and let me know, how was this guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You will find the complete outline of this guide on the home page.&lt;/p&gt;

</description>
      <category>css</category>
      <category>design</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
