<?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: Breindel Medina</title>
    <description>The latest articles on DEV Community by Breindel Medina (@kindadailybren).</description>
    <link>https://dev.to/kindadailybren</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%2F2659328%2F45321e04-ae9e-4f19-b1a2-f3c7f6b52a86.jpeg</url>
      <title>DEV Community: Breindel Medina</title>
      <link>https://dev.to/kindadailybren</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kindadailybren"/>
    <language>en</language>
    <item>
      <title>Automating Serverless: A Guide to CI/CD for AWS Lambda with GitHub Actions</title>
      <dc:creator>Breindel Medina</dc:creator>
      <pubDate>Tue, 11 Nov 2025 08:00:32 +0000</pubDate>
      <link>https://dev.to/kindadailybren/automating-serverless-a-guide-to-cicd-for-aws-lambda-with-github-actions-jh1</link>
      <guid>https://dev.to/kindadailybren/automating-serverless-a-guide-to-cicd-for-aws-lambda-with-github-actions-jh1</guid>
      <description>&lt;p&gt;Deploying serverless applications to the cloud has never been easier with AWS. You just upload your files as a zip to Lambda, turn on the function URL or integrate with an API Gateway, and voila! you are able to deploy your serverless app.&lt;/p&gt;

&lt;p&gt;But, this manual process has risks. You still have to manually check if the code is right. It might be that what you deploy is faulty, which causes your application to break. &lt;/p&gt;

&lt;p&gt;Since most developers upload their code to GitHub, what if there was a way to automate all of this checking and deployment with a single push to your repository?&lt;/p&gt;

&lt;p&gt;This is where GitHub Actions comes in.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is GitHub Actions?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform built directly into GitHub. It allows you to automate your software development workflows right from where your code lives.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At its core, GitHub Actions uses the following concepts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workflows&lt;/strong&gt;: These are automated processes defined in a YAML file (e.g., .github/workflows/deploy.yml). A workflow can be triggered by an event.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Events&lt;/strong&gt;: An event is a specific activity in your repository that triggers a workflow. The most common event is a push to a branch, but it can also be a pull_request, a new issue, or even a scheduled cron job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jobs&lt;/strong&gt;: A workflow is made up of one or more jobs. By default, jobs run in parallel. You can also configure them to run sequentially (e.g., a deploy job that runs only if a test job succeeds).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runners&lt;/strong&gt;: A runner is a server (a fresh virtual machine) that runs your workflow's jobs. GitHub provides runners for Linux, Windows, and macOS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps&lt;/strong&gt;: A job is a series of steps. A step can be a simple shell command (like pip install -r requirements.txt) or a pre-built, reusable command called an Action (like actions/checkout to check out your code).&lt;/p&gt;

&lt;p&gt;By combining these, you can create a workflow that, upon a push to your main branch, automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sets up a clean environment.&lt;/li&gt;
&lt;li&gt;Installs your dependencies.&lt;/li&gt;
&lt;li&gt;Runs your unit tests.&lt;/li&gt;
&lt;li&gt;If the tests pass, it securely deploys your code to AWS Lambda.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Demonstration
&lt;/h1&gt;

&lt;p&gt;Let's first start with a very simple code snippet which we will use in this demonstration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello dev.to article!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Code Breakdown (app.py):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;def lambda_handler(event, context)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the main function that AWS Lambda will call. event contains data about the trigger (e.g., from an API call), and context contains runtime information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;return { ... }&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The function returns a dictionary. For a Lambda function responding to an HTTP request (like from a Function URL or API Gateway), it must include statusCode (like 200 for "OK") and a body (which must be a string, hence json.dumps).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Prerequisite 2: Your Test File
&lt;/h2&gt;

&lt;p&gt;Let's now create our test file which our CI will use&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;test_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_simple_lambda_handler&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Call the handler
&lt;/span&gt;    &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Check the response
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello dev.to article!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# We run the test function directly
&lt;/span&gt;&lt;span class="nf"&gt;test_simple_lambda_handler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;All tests passed!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Code Breakdown (test_app.py):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;import app&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This line imports our app.py file so we can access its lambda_handler function.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;response = app.lambda_handler(event, context)&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We call our Lambda handler directly, passing in empty event and context objects (since our simple function doesn't use them).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;assert response['statusCode'] == 200&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the core of the test. assert checks if a statement is true. If it's false, the test fails and stops. Here, we check if the statusCode is 200.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;assert "..." in body['message']&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We check if the response message contains the text we expect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;test_simple_lambda_handler()&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We call the test function to run it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;print("All tests passed!")&lt;/code&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If all the assert statements pass, this line will run, letting us know our code works as expected.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Upload Your Code to GitHub
&lt;/h2&gt;

&lt;p&gt;Before we can set up GitHub Actions, our code needs to live in a GitHub repository. We'll assume you've already:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Created a new repository on GitHub.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Committed your files (app.py, test_app.py, requirements.txt).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pushed your code to the main branch.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Create AWS IAM User &amp;amp; Get Keys
&lt;/h2&gt;

&lt;p&gt;Now, let's get the AWS credentials GitHub Actions will use. We'll assume you're familiar with the AWS IAM console.&lt;/p&gt;

&lt;p&gt;You'll need to create a new IAM User with CLI access. Give it a descriptive name like github-lambda-deployer.&lt;/p&gt;

&lt;p&gt;For permissions, attach the AWSLambda_FullAccess policy.&lt;/p&gt;

&lt;p&gt;(Note: For a real-world project, it's best practice to create a more restrictive custom policy that only allows the lambda:UpdateFunctionCode and lambda:UpdateFunctionConfiguration actions on your specific function's ARN.)&lt;/p&gt;

&lt;p&gt;After creating the user, generate an Access key and Secret access key. Copy these immediately; you will need them for the next step.&lt;/p&gt;
&lt;h2&gt;
  
  
  Add Access Keys to GitHub Secrets
&lt;/h2&gt;

&lt;p&gt;Now let's securely store those keys in GitHub.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In your GitHub repository, go to Settings.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;In the left sidebar, navigate to Secrets and variables &amp;gt; Actions.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Click the New repository secret button.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Create two secrets, one for the Access Key, and one for the Secret Access Key:&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Click Add secret.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create the second secret:&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%2Fgzrsrv3hdtod4drlhtmi.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%2Fgzrsrv3hdtod4drlhtmi.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click Add secret.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should now have two secrets, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, listed under "Repository secrets." GitHub Actions can now access them using the ${{ secrets.NAME }} syntax.&lt;/p&gt;
&lt;h2&gt;
  
  
  Create Lambda Function
&lt;/h2&gt;

&lt;p&gt;Let's now create the AWS Lambda function that we will use.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a function with name "blog-func" and Python 3.13 runtime&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%2Fvjrskm0z0ay8s465yis9.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%2Fvjrskm0z0ay8s465yis9.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After it is done creating, Edit the handler from &lt;code&gt;lambda_handler.lambda_handler&lt;/code&gt; to &lt;code&gt;app.lamba_handler&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

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

&lt;p&gt;This is the heart of our pipeline. In your repository, create a new directory .github, and inside that, another directory workflows. Finally, create the file deploy.yml inside it. Replace the placeholders in this file before you commit.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;aws-region&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set this to the region where your Lambda function exists (e.g., us-west-2, ap-southeast-1).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;--function-name&lt;/code&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change YOUR-LAMBDA-FUNCTION-NAME to the exact name of your function in AWS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;File Path: .github/workflows/deploy.yml&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="c1"&gt;# Name of your workflow&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI-CD for AWS Lambda&lt;/span&gt;

&lt;span class="c1"&gt;# When this workflow is triggered&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;  &lt;span class="c1"&gt;# Run on pushes to the main branch&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;  &lt;span class="c1"&gt;# Run on pull requests targeting main&lt;/span&gt;

&lt;span class="c1"&gt;# A workflow run is made up of one or more jobs&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# The "test" job (Continuous Integration)&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Unit Tests&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt; &lt;span class="c1"&gt;# Use a Linux runner&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt; &lt;span class="c1"&gt;# Action to check out your code&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.11'&lt;/span&gt; &lt;span class="c1"&gt;# Use a specific Python version&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install test dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;# No dependencies needed for this simple handler&lt;/span&gt;
          &lt;span class="s"&gt;echo "No test dependencies to install."&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests (CI)&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;python test_app.py&lt;/span&gt;

  &lt;span class="c1"&gt;# The "deploy" job (Continuous Deployment)&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to AWS Lambda&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt; &lt;span class="c1"&gt;# This job will ONLY run if the "test" job succeeds&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout repository&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.11'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create Zip File&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;zip lambda_function.zip app.py&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS Credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# Use the secrets you created in Step 3&lt;/span&gt;
          &lt;span class="na"&gt;aws-access-key-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-secret-access-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_SECRET_ACCESS_KEY }}&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-1&lt;/span&gt; &lt;span class="c1"&gt;# Change to your Lambda's region&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to AWS Lambda (CD)&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;aws lambda update-function-code \&lt;/span&gt;
            &lt;span class="s"&gt;--function-name blog-func \ # Change to Lambda Name&lt;/span&gt;
            &lt;span class="s"&gt;--zip-file fileb://lambda_function.zip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Workflow Breakdown
&lt;/h3&gt;

&lt;p&gt;Let's break down that YAML file.&lt;/p&gt;

&lt;p&gt;The Triggers:&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;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This tells GitHub when to run the workflow. It will run on any push to the main branch, and also on any pull_request that targets the main branch. This is great for checking if a new change breaks the tests before you merge it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Job 1: test (The "CI" part)
&lt;/h3&gt;

&lt;p&gt;This job is our Continuous Integration check.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;name: Run Unit Tests&lt;/code&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The display name in the Actions tab.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;runs-on: ubuntu-latest&lt;/code&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It runs on a fresh Ubuntu virtual machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;uses: actions/checkout@v4&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This action checks out your repository code onto the runner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;uses: actions/setup-python@v5&lt;/code&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This action sets up the Python version we want.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;run: python test_app.py&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the key CI step. It runs our test file. If test_app.py fails (e.g., an assert is false), it will exit with an error, which fails the job and stops the entire pipeline.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Job 2: deploy (The "CD" part)
&lt;/h3&gt;

&lt;p&gt;This job is our Continuous Deployment step.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;needs: test&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the most important line for safety. It tells GitHub: Do not even start this job unless the test job finished successfully. This is what prevents you from deploying broken code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;run: zip lambda_function.zip app.py&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This bundles our app.py into the .zip file that Lambda requires.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;uses: aws-actions/configure-aws-credentials@v4&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is an official action from AWS. It uses the aws-access-key-id and aws-secret-access-key you provide to configure the AWS CLI on the runner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;with: ... ${{ secrets.AWS_ACCESS_KEY_ID }}&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is how you securely pass your GitHub Secrets to the action. They are injected at runtime and never printed in the logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;run: aws lambda update-function-code ...&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is the final AWS CLI command that actually performs the deployment, uploading your new lambda_function.zip to the function you specified.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Commit and Push
&lt;/h3&gt;

&lt;p&gt;Commit all your new files (specifically .github/workflows/deploy.yml) and push them to your main branch. Go to the "Actions" tab in your GitHub repository.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You will see your "CI-CD for AWS Lambda" workflow start.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;It will first run the test job. You can click to see the output, including "All tests passed!".&lt;/p&gt;

&lt;p&gt;Once the test job succeeds, the deploy job will begin. You will see it zip the code, configure credentials, and run the aws lambda update-function-code command.&lt;/p&gt;

&lt;p&gt;Check AWS: Go to your Lambda function in the AWS Console. You should see that the "Last modified" time has just updated. If you test your function in the console, it will now be running the code from your repository!&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%2F0lr746bjj0l5xtn7qa7m.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%2F0lr746bjj0l5xtn7qa7m.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also see the result by turning on the Function URL and opening it!&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%2Fh87ejw921cxlgpetv2gl.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%2Fh87ejw921cxlgpetv2gl.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;You now have a robust and automated CI/CD pipeline. Any time you push a change to your main branch, your code will be automatically tested, and if the tests pass, it will be securely deployed to AWS Lambda. This directly solves the problem of "manially checking" and deploying "faulty" code.&lt;/p&gt;

&lt;p&gt;From here, you can expand this pipeline to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use different branches for staging and production environments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Store your Lambda function name as a GitHub Secret instead of hard-coding it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a build step that installs requirements.txt into a folder before zipping.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run more complex integration tests that actually invoke the Lambda URL.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;That is it for this demonstration! Thank you for reading this informative blog, you can check out the code for this in my github&lt;/p&gt;

&lt;p&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/kindadailybren" rel="noopener noreferrer"&gt;
        kindadailybren
      &lt;/a&gt; / &lt;a href="https://github.com/kindadailybren/CI-CD-for-AWS-Lambda-with-GitHub-Actions-Blog" rel="noopener noreferrer"&gt;
        CI-CD-for-AWS-Lambda-with-GitHub-Actions-Blog
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;





</description>
      <category>webdev</category>
      <category>aws</category>
      <category>github</category>
      <category>programming</category>
    </item>
    <item>
      <title>Build a Simple Grocery Tracker App using Vue JS and Supabase</title>
      <dc:creator>Breindel Medina</dc:creator>
      <pubDate>Tue, 11 Nov 2025 07:58:29 +0000</pubDate>
      <link>https://dev.to/up_min_sparcs/build-a-simple-grocery-tracker-app-using-vue-js-and-supabase-51g6</link>
      <guid>https://dev.to/up_min_sparcs/build-a-simple-grocery-tracker-app-using-vue-js-and-supabase-51g6</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Sometimes, the best way to learn a new technology is to start building something&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What better way to do this than to pick a stack, spin up a project, and figure things out along the way?&lt;/p&gt;

&lt;p&gt;For this project, the stack is simple but powerful: Vue for the frontend and Supabase for the backend. Vue makes it easy to create responsive, dynamic interfaces, while Supabase handles the database, API, and even real-time functionality — all without needing to write a full backend from scratch.&lt;/p&gt;

&lt;p&gt;This article walks through the entire process — from setting things up to building core features like adding, updating, and deleting data, and finally exploring what could be improved or added next. Along the way, you’ll find practical examples, key takeaways, and code snippets that break down how each component works, helping you build a solid grasp of these technologies. By the end, you’ll (hopefully) feel confident enough to take what you’ve learned and start building your own projects using Vue and Supabase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack Breakdown
&lt;/h2&gt;

&lt;p&gt;This project is built using two main technologies: Vue and Supabase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vue&lt;/strong&gt; is a progressive JavaScript framework that is great for building reactive user interfaces. It’s lightweight, easy to learn, and provides a clean syntax that makes frontend development feel intuitive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supabase&lt;/strong&gt; is an open-source Firebase alternative that provides a full backend out of the box — including a PostgreSQL database, authentication, file storage, and auto-generated APIs. It’s developer-friendly, easy to set up, and integrates smoothly with frontend frameworks like Vue.&lt;/p&gt;

&lt;p&gt;Together, Vue and Supabase make a solid stack for quickly building modern web apps — without needing to spin up an entire backend from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vue, Supabase, and Why We Chose Them
&lt;/h2&gt;

&lt;p&gt;When trying to dive into something new, we knew we wanted a stack that was both easy to use and powerful enough to build something real. Vue and Supabase ended up being the perfect combo for that.&lt;/p&gt;

&lt;p&gt;We picked Vue because of how intuitive it is — the syntax is clean, straightforward, and just makes sense. It didn’t feel overwhelming to dive into, and building dynamic UIs felt smooth from the start.&lt;/p&gt;

&lt;p&gt;For the backend, we went with Supabase. It’s open source and super easy to set up — no complicated config or boilerplate. We got a fully functional backend out of the box, complete with a PostgreSQL database, authentication, file storage, and even real-time updates.&lt;/p&gt;

&lt;p&gt;Together, these two tools let us focus on actually building the project instead of getting stuck on setup. They gave us a fast way to go from an idea to a working app, while learning a lot in the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Frontend
&lt;/h2&gt;

&lt;p&gt;Alright, for this project, we’re keeping things light and fast by using Vite. &lt;/p&gt;

&lt;p&gt;What is &lt;strong&gt;Vite&lt;/strong&gt;? Think of it as a modern build tool that makes setting up and running your frontend super quick. &lt;/p&gt;

&lt;p&gt;Difference between Vite and Vue? &lt;br&gt;
Vite is just the tool that runs your Vue app behind the scenes. Vue handles the UI stuff, Vite handles the dev server and bundling.&lt;/p&gt;

&lt;p&gt;We’ll be using npm as our package manager, so make sure that’s installed. To get started, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm create vite@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Follow the prompts:&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%2Fa0ce9drot5qvjk6w2vtp.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%2Fa0ce9drot5qvjk6w2vtp.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Next up, we’ll set up Pinia for state management (a fancy way of saying “shared data between components”):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install pinia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Once that's installed, use this code in the main.ts:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { createPinia } from 'pinia'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This code sets up Pinia, which is a tool that helps your app share data between different components. First, it brings in Pinia, then creates a "store" (like a central place to keep your app's data). After that, it starts your Vue app and tells it to use that store so every part of the app can access and update shared data easily.&lt;/p&gt;

&lt;p&gt;After that, feel free to do a little cleaning. Delete any of the default files you’re not using. And that's it. Your frontend is ready to roll!&lt;br&gt;
Next up, backend!&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up Supabase
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Create a Supabase Account&lt;/strong&gt;&lt;br&gt;
First, you'll need a Supabase account. Just sign up, it's quick and they are offering a free tier. Once you're in, create a new project from the dashboard. For this project, we are going to use just the Supabase dashboard to configure our database. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure the Database&lt;/strong&gt;&lt;br&gt;
Head over to the "Table Editor" section. Here you can create new tables. Make a table called “product”. Add a few attributes or columns: id (UUID), name (varchar), quantity (int4), and price (numeric).&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Column Name     | Type
id          | UUID
name        | varchar
quantity    | int4
price       | numeric
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's all we need for this app. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect Supabase to Vue JS&lt;/strong&gt;&lt;br&gt;
Next, in your frontend project, make a directory called supabase and inside it, create a file named supabaseClient.ts. And, drop the following code:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { createClient } from "@supabase/supabase-js";
const supabase_url = import.meta.env.VITE_SUPABASE_URL;
const supabase_key = import.meta.env.VITE_SUPABASE_KEY;
export const supabase = createClient(supabase_url, supabase_key);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What this does is it sets up a connection between your app and your Supabase backend by creating a client using the project URL and API Key (p.s. store the url and key in your .env file for security). With that client, you are now connected to your hosted Supabase backend.&lt;/p&gt;
&lt;h2&gt;
  
  
  Components Structure
&lt;/h2&gt;

&lt;p&gt;In this section, you will see how the main components of the web app are implemented. Each code snippet is followed by an explanation to help you understand how the components work together to build interactive UI features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Button Component:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script lang="ts" setup&amp;gt;
defineProps&amp;lt;{ msg?: string }&amp;gt;()
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
    &amp;lt;button class="border-[1px] border-dashed px-4 py text-2xl cursor-pointer hover:bg-gray-300 transition-all duration-150"&amp;gt;{{ msg }}&amp;lt;/button&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This is a reusable button component. It accepts an optional msg prop to display text on the button. The button is styled with a dashed border, padding, large text, and a hover effect that changes the background color. It is used across different components for consistent styling and interactivity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Forms Component:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;section&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;div class="flex flex-col gap-4"&amp;gt;
        &amp;lt;div class="flex justify-between text-2xl xl:text-3xl items-center"&amp;gt;
          &amp;lt;p&amp;gt;{{ nameRef }}&amp;lt;/p&amp;gt;
          &amp;lt;div class="flex gap-10"&amp;gt;
            &amp;lt;div class="flex gap-2 xl:gap-4"&amp;gt;
              &amp;lt;p&amp;gt;&amp;amp;#x20B1 {{ priceRef }}&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;x&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;{{ quantityRef }}&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div&amp;gt;
              &amp;lt;button class="hover:cursor-pointer" @click="isEditing = !isEditing"&amp;gt;
                Edit
              &amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/section&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This part of the Forms component displays the product's name, price, and quantity in a well-structured layout. There’s an "Edit" button that toggles the editing mode by changing the isEditing value.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!-- edit state --&amp;gt;
&amp;lt;div v-if="isEditing" class="flex flex-col gap-2"&amp;gt;
  &amp;lt;div class="flex items-center justify-between text-2xl gap-4"&amp;gt;
    &amp;lt;input v-model="nameRef" type="text" :placeholder="product?.name" class="w-full border-b border-dashed px-4" @keydown.enter="update" /&amp;gt;
    &amp;lt;div class="flex items-center justify-center gap-1"&amp;gt;
      &amp;lt;Button @click="decrement" msg="-" /&amp;gt;
      &amp;lt;Button @click="quantityRef++" msg="+" class="hover:bg-green-400" /&amp;gt;
      &amp;lt;button @click="deleteProd" class="border-[1px] border-dashed px-4 py-1 text-2xl cursor-pointer hover:bg-red-400 transition-all duration-150"&amp;gt;
        &amp;lt;Trash class="w-6 h-6" /&amp;gt;
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;Button @click="update" msg="Save" class="hover:bg-green-400" /&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;When editing is enabled, the user can change the product name, increase or decrease the quantity, or delete the product. It uses the Button component for interactivity and consistent styling.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script setup lang="ts"&amp;gt;
import { ref, computed } from 'vue'
import { updateProduct, deleteProduct } from '../utils/actions.ts'
import Button from './Button.vue'
import Trash from './Trash.vue'
import { useAllProductsStore } from '../stores/allProducts.ts'

const productStore = useAllProductsStore()

const props = defineProps({
  id: { type: String, required: true },
});

const product = computed(() =&amp;gt;
  productStore.products.find((p) =&amp;gt; p.id === props.id)
)

const nameRef = ref(product.value?.name ?? "")
const priceRef = ref(product.value?.price ?? 0)
const quantityRef = ref(product.value?.quantity ?? 0)

const update = async () =&amp;gt; {
  isEditing.value = !isEditing.value
  if (!product.value) return;
  await updateProduct(product.value.id, nameRef.value, quantityRef.value, priceRef.value)
  isEditing.value = false
}

const deleteProd = async () =&amp;gt; {
  await deleteProduct(props.id);
};

const decrement = () =&amp;gt; {
  if (quantityRef.value &amp;gt; 1) quantityRef.value--
}
const isEditing = ref(false)
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This script defines the reactive variables and functions used in the component. It connects with the Pinia store to find and update products. It also handles product deletion and quantity adjustment, toggled by the edit state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Music Player Component:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class="flex gap-10"&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;button v-if="isPlaying" @click="() =&amp;gt; { play(); isPlaying = false; }" class="bg-green-400 px-8 py-2 hover:bg-green-500 cursor-pointer"&amp;gt;PLAY&amp;lt;/button&amp;gt;
      &amp;lt;button v-else @click="() =&amp;gt; { stop(); isPlaying = true; }" class="bg-red-400 px-8 py-2 hover:bg-red-500 cursor-pointer"&amp;gt;STOP&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;button v-if="isPlaying2" @click="() =&amp;gt; { play2(); isPlaying2 = false; }" class="bg-yellow-400 px-8 py-2 hover:bg-orange-500 cursor-pointer"&amp;gt;PLAY&amp;lt;/button&amp;gt;
      &amp;lt;div v-else class="flex flex-col justify-center"&amp;gt;
        &amp;lt;button @click="() =&amp;gt; { pause(); isPlaying2 = true; }" class="bg-red-400 px-8 py-2 hover:bg-red-500 cursor-pointer"&amp;gt;PAUSE&amp;lt;/button&amp;gt;
        &amp;lt;div class="flex-col text-center text-white hidden"&amp;gt;
          &amp;lt;p class="text-2xl"&amp;gt;Playing Naaalala Ka&amp;lt;/p&amp;gt;
          &amp;lt;p&amp;gt;by&amp;lt;/p&amp;gt;
          &amp;lt;p class="text-2xl"&amp;gt;Rey Valera&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This component provides buttons to control two separate audio tracks. One plays background music, and the other plays a specific song. Buttons toggle between play and stop/pause modes
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script lang="ts" setup&amp;gt;
import { ref } from 'vue';
import { useSound } from '@vueuse/sound';
import bgMusic from '../assets/sound/bg-music-grocery-store.mp3'
import reyValera from '../assets/sound/naaalala-ka-rey-valera.mp3'

const isPlaying = ref(true);
const isPlaying2 = ref(true);

const { play, stop } = useSound(bgMusic, {
  volume: 0.5,
  loop: true,
  autoplay: true,
})

const { play: play2, pause } = useSound(reyValera, {
  volume: 0.2,
  loop: false,
  autoplay: true,
})
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This music player provides play/pause functionality for two audio tracks. It uses the @vueuse/sound library to control audio playback and manages playback states with Vue's reactivity system.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  View, Add, Update, Delete Functionality
&lt;/h2&gt;

&lt;p&gt;In this section of the article, you will see how the CRUD functionalities of the web app are implemented with the following code. The code may look intimidating, but this section will also explain key terms and syntax for your understanding.&lt;/p&gt;

&lt;p&gt;An important concept and syntax that needs to be understood to fully grasp the code is async and await.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;async&lt;/code&gt; is a keyword used to declare a function as asynchronous. It allows the use of await inside it and ensures the function returns a promise.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;await&lt;/code&gt; is used inside an async function to pause the execution of the function until the awaited promise is resolved or rejected. This helps write asynchronous code in a cleaner, more readable way, as if it were synchronous.&lt;/p&gt;

&lt;p&gt;Now that you know the basics of async and await, let's proceed with the code implementation of the CRUD.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;View All Items:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async fetchProducts() {
  this.loading = true;
  const { data, error } = await supabase.from("product").select("*");
  if (error) {
    console.error("Error fetching products:", error);
    this.loading = false;
    return;
  }
  this.products = data as Product[];
  this.loading = false;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This function retrieves all items from the product table using Supabase. It sets a loading flag to true while fetching and resets it afterward. If there's an error during the fetch, it logs the error; otherwise, it stores the result in the products variable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Add Items:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const addItem = async (
  name: string,
  price: number,
  quantity: number,
) =&amp;gt; {
  const { data, error } = await supabase
    .from("product")
    .insert([{ name, price, quantity }]);
  if (error) console.error("Insert failed", error);
  else console.log("Inserted item", data);
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This function inserts a new product into the &lt;code&gt;product&lt;/code&gt; table. It accepts the item's name, price, and quantity as arguments. If the insertion fails, it logs an error; otherwise, it confirms the item was successfully inserted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Update Items:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const updateProduct = async (
  id: string,
  productName: string,
  quantity: number,
  price: number,
) =&amp;gt; {
  const { data, error } = await supabase
    .from("product")
    .update({
      name: productName,
      quantity: quantity,
      price: price,
    })
    .eq("id", id);

  if (error) {
    console.error("Update error:", error.message);
  } else {
    console.log("Updated successfully:", data);
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This function updates an existing product in the table. It uses the product's id to find the specific item and updates its name, quantity, and price. An error message is shown if the update fails, otherwise it logs the updated data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Delete Items:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const deleteProduct = async (id: string) =&amp;gt; {
  const { data, error } = await supabase
    .from("product") // your table name
    .delete()

    .eq("id", id);

  if (error) {
    console.error("Delete error:", error.message);
  } else {
    console.log("Deleted row:", data);
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;This function deletes a product from the table using its id. It filters the product to delete using the .eq("id", id) method. If an error occurs, it logs the error message; otherwise, it confirms the deletion.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Now that you've seen the essential features of this app, here are some ideas you can implement on your own to go even further. They're great opportunities to deepen your knowledge of Vue and Supabase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authentication System (Auth)&lt;/strong&gt;: Try adding user login and registration. It’s a great way to learn about route protection and Supabase auth integration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advanced Filtering and Sorting&lt;/strong&gt;: Add features like search bars or dropdowns to filter and sort items by name, price, or quantity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Different Lists for Different Users&lt;/strong&gt;: Modify your database structure to include a column that assigns each item to a specific user. Then filter data based on who's logged in so each user sees only their own list.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for checking out our article! We hope this gave you not just insight into building with Vue and Supabase, but also ideas you’re excited to try out.&lt;/p&gt;

&lt;p&gt;If you're curious to explore the actual code or want to see the app in action, feel free to check out our GitHub repository and live demo:&lt;/p&gt;

&lt;p&gt;GitHub Repo: &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/kindadailybren" rel="noopener noreferrer"&gt;
        kindadailybren
      &lt;/a&gt; / &lt;a href="https://github.com/kindadailybren/groceryTracker_SPARCS" rel="noopener noreferrer"&gt;
        groceryTracker_SPARCS
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🛒 groceryTracker_SPARCS&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A simple and efficient grocery tracking app built with Vue 3, TypeScript, and Vite. This project is designed to help users manage grocery items through a clean interface, with fast performance powered by Vite and strong type safety from TypeScript.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Features&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;✅ Add and remove grocery items
📦 View list of tracked items&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📦 Tech Stack&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Framework: Vue 3&lt;/p&gt;
&lt;p&gt;Build Tool: Vite&lt;/p&gt;
&lt;p&gt;Language: TypeScript&lt;/p&gt;
&lt;p&gt;State Management: Pinia (planned/included)&lt;/p&gt;
&lt;p&gt;Backend: Supabase (optional, based on context)&lt;/p&gt;
&lt;p&gt;🛠️ Project Setup&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;# Clone the repository
git clone https://github.com/kindadailybren/groceryTracker_SPARCS.git
cd groceryTracker_SPARCS

# Install dependencies
npm install

# Start development server
npm run dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🧪 Scripts&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Command   Description
&lt;code&gt;npm run dev&lt;/code&gt;    Start local dev server
&lt;code&gt;npm run build&lt;/code&gt;  Build for production
&lt;code&gt;npm run lint&lt;/code&gt;   Lint the code&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;👨‍💻 Author&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Errol Minguez&lt;br&gt;
UP Mindanao · BS Computer Science&lt;br&gt;
Breindel Medina&lt;br&gt;
UP Mindanao · BS Computer Science&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📄 License&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;This project is licensed under the…&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/kindadailybren/groceryTracker_SPARCS" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Live Demo: &lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://grocery-tracker-sparcs.vercel.app/" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;grocery-tracker-sparcs.vercel.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Explore the codebase, try running it locally, or even fork it and start building your own version!&lt;/p&gt;

</description>
      <category>vue</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
