<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ api - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ api - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 23 May 2026 22:20:07 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/api/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Bypass Cloud SMTP Restrictions Using Brevo and HTTP APIs ]]>
                </title>
                <description>
                    <![CDATA[ Being able to communicate by sending emails through web applications is important these days. It helps businesses stay connected with their potential customers, securely verify user identities, and de ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-bypass-cloud-smtp-restrictions-using-brevo-and-http-apis/</link>
                <guid isPermaLink="false">69fe2a2ef239332df4f7f8b9</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ brevo ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google smtp ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cloud-smtp ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Okoro Emmanuel Nzube ]]>
                </dc:creator>
                <pubDate>Fri, 08 May 2026 18:23:42 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/64f68792-e18c-4b90-9c65-c6d9884ab191.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Being able to communicate by sending emails through web applications is important these days. It helps businesses stay connected with their potential customers, securely verify user identities, and deliver crucial notifications like password resets.</p>
<p>But sometimes, deploying your perfectly working email function to the cloud leads to unexpected and frustrating errors. You build your backend, test it locally, and it works flawlessly. Then you deploy to the cloud, and suddenly your app stops sending emails completely.</p>
<p>In this article, you’ll learn exactly why your email setup fails on cloud platforms like Render or Heroku, the underlying networking rules causing the issue, and how to elegantly bypass these restrictions using Brevo's HTTP API.</p>
<p>Let’s dive right in.</p>
<h2 id="heading-outline">Outline</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-tools-well-be-using">Tools We'll Be Using</a></p>
</li>
<li><p><a href="#heading-the-problem-nodemailer-and-smtp-blocking">The Problem: Nodemailer and SMTP Blocking</a></p>
</li>
<li><p><a href="#heading-the-modern-trap-domain-verification">The "Modern" Trap: Domain Verification</a></p>
</li>
<li><p><a href="#heading-the-ultimate-solution-brevo-and-http-apis">The Ultimate Solution: Brevo and HTTP APIs</a></p>
</li>
<li><p><a href="#heading-backend-setup">Backend Setup</a></p>
</li>
<li><p><a href="#heading-brevo-configuration-setup">Brevo Configuration Setup</a></p>
</li>
<li><p><a href="#heading-creating-the-email-function">Creating the Email Function</a></p>
</li>
<li><p><a href="#heading-integrating-the-function-into-an-express-route">Integrating the Function into an Express Route</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To get the absolute most out of this tutorial, it’s important to have some basic knowledge of the following:</p>
<ul>
<li><p><strong>JavaScript and Node.js:</strong> Having a good fundamental understanding of how JS works on the server side will make it easier to follow along with the project.</p>
</li>
<li><p><strong>REST APIs:</strong> You should have a basic understanding of how HTTP requests (like POST and GET) work using native <code>fetch()</code> in Node.js.</p>
</li>
<li><p><strong>Express.js:</strong> A little background on creating basic server routes will be helpful, as we'll build a real-world controller.</p>
</li>
<li><p>A basic understanding of what <a href="https://nodemailer.com/">Nodemailer</a> is and how cloud hosting platforms (like Render or Heroku) operate.</p>
</li>
</ul>
<h2 id="heading-tools-well-be-using">Tools We’ll Be Using</h2>
<p>In one of my recent projects, I created a complex authentication flow where users needed an OTP (One Time Password) sent to their email to complete registration. I set up Nodemailer, linked my Gmail, and tested it on <code>localhost</code>. Within seconds, the emails arrived perfectly.</p>
<p>But when I deployed my backend to Render, the entire signup flow broke. After doing some deep digging, I found out why it broke and how to fix it permanently. And now that I know how it works, I wanted to share it with you all.</p>
<h2 id="heading-the-problem-nodemailer-and-smtp-blocking">The Problem: Nodemailer and SMTP Blocking</h2>
<p>So what exactly is the issue?</p>
<p>Nodemailer is a very popular Node.js module that lets you send emails efficiently. Usually, developers use it to connect to services like Gmail or Mailtrap using <strong>SMTP</strong> (Simple Mail Transfer Protocol). When your code tries to send an email, Nodemailer opens a connection to the mail server using Port <code>587</code> (for STARTTLS) or Port <code>465</code> (for SSL).</p>
<p>But cloud providers like Render, Heroku, DigitalOcean, and AWS face a massive daily battle against automated spammers. Malicious users often spin up thousands of free-tier servers specifically to blast out millions of spam emails. If a cloud provider allows this, their entire network IP address block will get blacklisted by Gmail, Outlook, and Yahoo.</p>
<p>To protect their network reputation, cloud providers enacted a heavy-handed, silent rule: <strong>All outbound traffic on Ports 25, 465, and 587 is strictly blocked on free and entry-level tiers.</strong></p>
<p>This means your server is literally trapped behind a firewall. If you check your server logs, you won't see an "Invalid Password" error. Instead, you'll see a timeout error that looks like this:</p>
<pre><code class="language-plaintext">Error: connect ETIMEDOUT 142.250.102.108:587
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1494:16)
</code></pre>
<p>Your code isn't broken – it's just being blocked at the network level!</p>
<h3 id="heading-the-modern-trap-domain-verification">The "Modern" Trap: Domain Verification</h3>
<p>When developers hit this wall, they often try modern API-based email services like Resend or SendGrid. These are amazing tools, but they introduce a new problem for beginners: <strong>Strict Domain Authentication.</strong></p>
<p>To use Resend in production, you must own a custom domain (like <code>yourname.com</code>) and configure DNS records (SPF, DKIM, and DMARC). If you don't own a domain, Resend's sandbox mode strictly restricts you to sending emails <em>only</em> to yourself. You can't send emails to your live users.</p>
<p>For a developer just trying to launch a portfolio project, buying a domain just to send test emails is a huge bottleneck.</p>
<h3 id="heading-the-ultimate-solution-brevo-and-http-apis">The Ultimate Solution: Brevo and HTTP APIs</h3>
<p>We need a solution that meets two criteria:</p>
<ol>
<li><p>It must bypass the Port <code>587</code> firewall.</p>
</li>
<li><p>It must let us send emails to <em>anyone</em> without forcing us to buy a custom domain.</p>
</li>
</ol>
<p>This is where the architectural difference between SMTP and REST APIs comes to the rescue. While SMTP is a dedicated protocol for routing mail, a REST API operates over standard web traffic using <strong>HTTPS (Port 443)</strong>. Cloud providers <em>can't</em> block Port 443, because doing so would prevent your server from fetching data from databases or functioning as a web server entirely.</p>
<p>Enter <strong>Brevo</strong> (formerly Sendinblue). Brevo is a powerful email platform that allows you to send emails via a standard REST API. Best of all, their free tier (300 emails/day) allows Single Sender Verification. You just verify your standard Gmail address, and they let you send to anyone!</p>
<p>By sending a JSON payload via HTTPS to Brevo's API, your server routes the traffic out of the unrestricted Port <code>443</code>, bypassing the Render firewall completely.</p>
<p>Now that you know the theory behind the tools we’ll be using, let’s move on to writing the code.</p>
<h2 id="heading-backend-setup">Backend Setup</h2>
<p>First things first, you have to set up your environment. If you don't already have Node.js installed on your computer, head to their <a href="https://nodejs.org/en">website</a> to download and install it.</p>
<p>Start by running <code>npm init -y</code> in your terminal. This creates the <code>package.json</code> file which manages your project and stores all the dependencies.</p>
<p>Next, run <code>npm install express dotenv</code>.</p>
<p>You might be used to installing <code>nodemailer</code> for your email tasks. But because we are going to use the native Node.js <code>fetch()</code> API to talk to the Brevo API, you actually don't need to install <em>any</em> heavy email libraries at all! We want to keep our backend as lightweight as possible.</p>
<h3 id="heading-brevo-configuration-setup">Brevo Configuration Setup</h3>
<p>Before you write the email function, you first need to configure Brevo to get access to your API key.</p>
<ol>
<li><p>Go to <a href="https://www.brevo.com/">Brevo.com</a> and create a free account.</p>
</li>
<li><p>During setup, they will ask you to add a <strong>Sender Email</strong>. Make sure you input your standard Gmail address. They will send you an email with a link to verify you own this address.</p>
</li>
<li><p>Once verified and inside the dashboard, click on your profile name in the top right corner, and select <strong>SMTP &amp; API</strong> from the dropdown menu.</p>
</li>
<li><p>Go to the <strong>API Keys</strong> tab and click <strong>Generate a new API key</strong>. Give it a name like "MyWebApp".</p>
</li>
</ol>
<p>Copy this generated key and store it safely in a <code>.env</code> file at the root of your project:</p>
<pre><code class="language-env"># .env file
EMAIL_USER = yourverifiedemail@gmail.com
BREVO_API_KEY = xkeysib-your-generated-api-key-goes-here
</code></pre>
<h3 id="heading-creating-the-email-function">Creating the Email Function</h3>
<p>Now that you’ve gotten your API key and set up your environment variables, all that remains is to start putting your backend code together.</p>
<p>Create a file named <code>utils/email.js</code>.</p>
<p>First, start by ensuring you can load your <code>.env</code> file so you can easily access the credentials you generated:</p>
<pre><code class="language-javascript">require("dotenv").config();

// We'll define the function to accept dynamic options
const sendEmail = async (options) =&gt; {
  const brevoApiKey = process.env.BREVO_API_KEY;
  const senderEmail = process.env.EMAIL_USER;

  // Validate that the keys actually exist
  if (!brevoApiKey || !senderEmail) {
    throw new Error("Missing Brevo credentials in environment variables.");
  }
</code></pre>
<p>Next on the line, you’ll need to structure your payload. This is the JSON object that tells Brevo exactly who is sending the email, who is receiving it, and what the content is. Here’s how you can do that:</p>
<pre><code class="language-javascript">  const payload = {
    sender: {
      name: "My Awesome Web App",
      email: senderEmail, // Must match your verified Brevo email
    },
    to: [
      {
        email: options.email, // The dynamic email address of the user receiving the email
      },
    ],
    subject: options.subject,
    htmlContent: options.html,
  };
</code></pre>
<p>In the code above, the <code>payload</code> object securely packages up your information. We pass in <code>options.email</code>, <code>options.subject</code>, and <code>options.html</code> so that we can reuse this single function for welcome emails, password resets, and notifications.</p>
<p>Now, create the actual network request that sends your data to the Brevo backend. We'll use the <code>POST</code> method. When the data is sent, it must be stringified into a JSON format.</p>
<pre><code class="language-javascript">  try {
    const response = await fetch("https://api.brevo.com/v3/smtp/email", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "api-key": brevoApiKey,
      },
      body: JSON.stringify(payload),
    });

    const result = await response.json();

    if (!response.ok) {
      throw new Error(`Brevo API Error: ${JSON.stringify(result)}`);
    }

    console.log(`Email successfully sent to ${options.email} via Brevo HTTP API!`);
  } catch (error) {
    console.error("Error details:", error.message);
  }
};

module.exports = sendEmail;
</code></pre>
<p>In the code above, after the payload is submitted, if the message is sent successfully, a success log will be displayed in your terminal. But if the message wasn’t successful – maybe due to a typo in your API key – an error message will be thrown to help you debug exactly what went wrong.</p>
<h3 id="heading-integrating-the-function-into-an-express-route">Integrating the Function into an Express Route</h3>
<p>At this point, you've successfully built a robust email function. Let's see how you would actually use this in a real Express application.</p>
<p>Create an <code>index.js</code> file and set up a simple Express server route:</p>
<pre><code class="language-javascript">const express = require("express");
const sendEmail = require("./utils/email");
const app = express();

app.use(express.json()); // Middleware to parse JSON request bodies

app.post("/api/signup", async (req, res) =&gt; {
  const { username, email } = req.body;

  // 1. Save user to database (skipped for brevity)
  
  // 2. Generate a random OTP
  const otp = Math.floor(100000 + Math.random() * 900000);

  // 3. Send the email using our new Brevo function
  try {
    await sendEmail({
      email: email,
      subject: "Welcome! Here is your Verification Code",
      html: `
        &lt;div style="font-family: sans-serif; text-align: center;"&gt;
          &lt;h2&gt;Welcome to My Awesome Web App, ${username}!&lt;/h2&gt;
          &lt;p&gt;Please use the verification code below to complete your registration:&lt;/p&gt;
          &lt;h1 style="color: #2563eb; letter-spacing: 5px;"&gt;${otp}&lt;/h1&gt;
          &lt;p&gt;This code will expire in 10 minutes.&lt;/p&gt;
        &lt;/div&gt;
      `,
    });

    res.status(201).json({ message: "User created and email sent!" });
  } catch (error) {
    res.status(500).json({ error: "Failed to send email." });
  }
});

app.listen(8000, () =&gt; {
  console.log("Server running on port 8000");
});
</code></pre>
<p>And that is it! You can now hit this <code>/api/signup</code> endpoint from your React or Vue frontend, and it will instantly fire off a beautifully formatted email via Brevo's REST API.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>As developers, encountering a bug that works locally but fails in production is a rite of passage. But the "Email Delivery Failed" timeout error is special. It teaches you that software engineering isn't just about writing clean syntax – it's about understanding the underlying infrastructure, network layers, and the security context of the environment your code runs in.</p>
<p>By swapping a protocol (SMTP) for an architectural pattern (REST API over HTTPS), you didn't just fix a bug. You successfully engineered a secure, free, and robust bypass around a cloud-level firewall without relying on heavy third-party NPM modules like Nodemailer.</p>
<p>If you've made it this far, I hope I've successfully shown you the importance of understanding network layers and how you can use HTTP APIs to send email messages directly from your web applications safely.</p>
<p>Thank you for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Handle Stripe Webhooks Reliably with Background Jobs ]]>
                </title>
                <description>
                    <![CDATA[ You've set up Stripe. Checkout works. Customers can pay. But what happens after payment? The webhook handler is where most payment integrations silently break. Your server crashes halfway through gran ]]>
                </description>
                <link>https://www.freecodecamp.org/news/stripe-webhooks-background-jobs/</link>
                <guid isPermaLink="false">69e8f14f5d1c10710571b1ae</guid>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Magnus Rødseth ]]>
                </dc:creator>
                <pubDate>Wed, 22 Apr 2026 16:03:27 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/460d0b4c-c95d-4356-a6df-a0c0c52b78b6.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>You've set up Stripe. Checkout works. Customers can pay. But what happens <em>after</em> payment?</p>
<p>The webhook handler is where most payment integrations silently break. Your server crashes halfway through granting access. Your email service is down when you try to send the confirmation. Your database times out during a write.</p>
<p>Stripe retries the entire webhook, but your handler already sent the confirmation email before it crashed. Now the customer gets two emails and no access.</p>
<p>This article shows you how to fix this. You'll learn how to build webhook handlers that survive failures by splitting your post-payment logic into durable, independently retried steps. The pattern works for any multi-step webhook processing, not just Stripe.</p>
<p>Here's what you'll learn:</p>
<ul>
<li><p>Why Stripe webhooks fail silently in production</p>
</li>
<li><p>How a naïve inline handler breaks under real-world conditions</p>
</li>
<li><p>The pattern: webhook receives, validates, and enqueues (nothing more)</p>
</li>
<li><p>How to build a durable purchase flow with individually checkpointed steps</p>
</li>
<li><p>How to handle refunds and abandoned checkouts with the same pattern</p>
</li>
<li><p>How to test webhook handlers locally</p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along, you should be familiar with:</p>
<ul>
<li><p>Node.js and TypeScript</p>
</li>
<li><p>Basic Stripe integration (checkout sessions, webhooks)</p>
</li>
<li><p>SQL databases (the examples use PostgreSQL with Drizzle ORM)</p>
</li>
<li><p>npm or any Node.js package manager</p>
</li>
</ul>
<p>You don't need prior experience with Inngest or durable execution. This article explains both from scratch.</p>
<h3 id="heading-what-you-need-to-install">What You Need to Install</h3>
<p>If you want to run the code examples, install these packages:</p>
<pre><code class="language-bash">npm install inngest stripe drizzle-orm @react-email/components resend
</code></pre>
<p>You'll also need the <a href="https://stripe.com/docs/stripe-cli">Stripe CLI</a> for local webhook testing. Install it via Homebrew on macOS (<code>brew install stripe/stripe-cli/stripe</code>) or follow the instructions in Stripe's documentation for other platforms.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-why-stripe-webhooks-fail-silently">Why Stripe Webhooks Fail Silently</a></p>
</li>
<li><p><a href="#heading-the-naive-approach-and-why-it-breaks">The Naïve Approach (and Why It Breaks)</a></p>
</li>
<li><p><a href="#heading-the-pattern-webhook-to-event-to-durable-function">The Pattern: Webhook to Event to Durable Function</a></p>
</li>
<li><p><a href="#heading-how-to-set-up-the-webhook-endpoint">How to Set Up the Webhook Endpoint</a></p>
</li>
<li><p><a href="#heading-how-to-build-a-durable-purchase-flow">How to Build a Durable Purchase Flow</a></p>
</li>
<li><p><a href="#heading-how-to-handle-refunds-with-the-same-pattern">How to Handle Refunds with the Same Pattern</a></p>
</li>
<li><p><a href="#heading-how-to-recover-abandoned-checkouts">How to Recover Abandoned Checkouts</a></p>
</li>
<li><p><a href="#heading-how-to-test-webhook-handlers-locally">How to Test Webhook Handlers Locally</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-why-stripe-webhooks-fail-silently">Why Stripe Webhooks Fail Silently</h2>
<p>The happy path is easy. A customer pays, Stripe sends a <code>checkout.session.completed</code> event to your server, and your handler processes it. In development, this works every time.</p>
<p>Production is different: Your webhook handler typically needs to do several things after a successful payment. It looks up the user in the database, records the purchase, sends a confirmation email, notifies the admin, grants access to the product (maybe via a GitHub invitation or an API key), and schedules follow-up emails. That's five or six operations involving three or four external services.</p>
<p>Here are the failure modes that will eventually hit your webhook handler:</p>
<h4 id="heading-1-your-server-crashes-mid-processing">1. Your server crashes mid-processing</h4>
<p>The database write succeeded, but the email never sent. Stripe retries the webhook, and your handler runs again.</p>
<p>Now you have a duplicate database entry or a unique constraint error that kills the retry.</p>
<h4 id="heading-2-an-external-service-is-temporarily-down">2. An external service is temporarily down</h4>
<p>Your email provider returns a 500. Your GitHub API call gets rate-limited. Your analytics service times out.</p>
<p>The webhook handler throws, and Stripe retries the entire thing. But the steps that already succeeded (the database write, the first email) run again.</p>
<h4 id="heading-3-the-handler-times-out">3. The handler times out</h4>
<p>Stripe expects a 2xx response within about 20 seconds. If your handler does too much work, Stripe marks it as failed and retries. Your handler may have partially completed before the timeout.</p>
<h4 id="heading-4-partial-completion-with-no-rollback">4. Partial completion with no rollback</h4>
<p>This is the worst failure mode. Steps 1 through 3 succeed. Step 4 fails. Stripe retries, and steps 1 through 3 run again.</p>
<p>The customer gets two confirmation emails. The database gets a duplicate record. But step 4 still fails because the underlying issue (a rate limit, a service outage) hasn't been resolved.</p>
<h4 id="heading-5-race-conditions-on-retry">5. Race conditions on retry</h4>
<p>Stripe can deliver the same event more than once even without a failure on your end. Network glitches, load balancer timeouts, and Stripe's own retry logic mean your handler must be prepared for duplicate deliveries. If your handler isn't idempotent at every step, duplicates compound the partial-completion problem.</p>
<p>Stripe's retry behavior is well-designed. It uses exponential backoff and retries up to dozens of times over several days. But Stripe retries the <em>entire webhook delivery</em>.</p>
<p>It has no way to know that your handler completed steps 1 through 3 and only needs to retry step 4. That distinction is your responsibility.</p>
<p>The core problem is that your webhook handler does too many things in a single request. Every external call is a potential failure point, and you have no checkpointing between them. When one fails, you lose track of which ones already succeeded.</p>
<h2 id="heading-the-naive-approach-and-why-it-breaks">The Naïve Approach (and Why It Breaks)</h2>
<p>Here's what a typical webhook handler looks like. I've seen hundreds of variations of this pattern across codebases, tutorials, and Stack Overflow answers:</p>
<pre><code class="language-typescript">app.post("/api/payments/webhook", async (req, res) =&gt; {
  const event = stripe.webhooks.constructEvent(
    req.body,
    req.headers["stripe-signature"],
    process.env.STRIPE_WEBHOOK_SECRET
  );

  if (event.type === "checkout.session.completed") {
    const session = event.data.object;

    // Step 1: Look up the user
    const user = await db.users.findOne({ id: session.metadata.userId });

    // Step 2: Record the purchase
    await db.purchases.insert({
      userId: user.id,
      stripeSessionId: session.id,
      amount: session.amount_total,
      status: "completed",
    });

    // Step 3: Send confirmation email
    await sendEmail({
      to: user.email,
      subject: "Purchase confirmed!",
      template: "purchase-confirmation",
    });

    // Step 4: Grant product access (GitHub repo invitation)
    await addCollaborator(user.githubUsername);

    // Step 5: Send access email
    await sendEmail({
      to: user.email,
      subject: "Your repository access is ready!",
      template: "repo-access",
    });

    // Step 6: Track analytics
    await analytics.track(user.id, "purchase_completed", {
      amount: session.amount_total,
    });
  }

  res.json({ received: true });
});
</code></pre>
<p>This looks clean. It reads top-to-bottom. Every tutorial teaches it this way.</p>
<p>Now walk through what happens when step 4 fails. Maybe GitHub's API is rate-limited and the <code>addCollaborator</code> call throws an error. Your handler returns a 500 to Stripe.</p>
<p>Here is the state after the failure:</p>
<ul>
<li><p>The user exists in the database (step 1 was just a lookup, no problem).</p>
</li>
<li><p>A purchase record was created (step 2 succeeded).</p>
</li>
<li><p>The confirmation email was sent (step 3 succeeded).</p>
</li>
<li><p>GitHub access was <strong>not</strong> granted (step 4 failed).</p>
</li>
<li><p>The access email was <strong>not</strong> sent (step 5 never ran).</p>
</li>
<li><p>Analytics were <strong>not</strong> tracked (step 6 never ran).</p>
</li>
</ul>
<p>Stripe retries the webhook. Your handler runs again from the top:</p>
<ul>
<li><p>Step 1: Looks up the user again. Fine.</p>
</li>
<li><p>Step 2: Tries to insert another purchase record. If you have a unique constraint on <code>stripeSessionId</code>, this throws. If you don't, you now have a duplicate.</p>
</li>
<li><p>Step 3: Sends the confirmation email again. The customer gets a second "Purchase confirmed!" email.</p>
</li>
<li><p>Step 4: Tries GitHub access again. Maybe it works this time, maybe not.</p>
</li>
<li><p>Steps 5-6: May or may not run depending on step 4.</p>
</li>
</ul>
<p>You can patch this with idempotency checks: "if purchase already exists, skip step 2." But now your handler is full of conditional logic for every step. And you still have the duplicate email problem, because there's no way to check "did I already send this email?" without building your own tracking system.</p>
<p>This approach doesn't scale. Every new step adds another failure mode, another idempotency check, and another edge case.</p>
<h2 id="heading-the-pattern-webhook-to-event-to-durable-function">The Pattern: Webhook to Event to Durable Function</h2>
<p>The fix is a separation of concerns. Your webhook handler should do exactly one thing: validate the incoming event and enqueue it for processing. Nothing else.</p>
<p>All the actual work (database writes, emails, API calls, analytics) moves into a durable background function where each step is individually checkpointed, retried, and tracked.</p>
<p>Here's the flow:</p>
<pre><code class="language-text">Stripe webhook
    |
    v
Webhook endpoint (validate signature, extract event, enqueue)
    |
    v
Background job system (receives event)
    |
    v
Durable function
    |-- Step 1: Look up user and purchase (checkpointed)
    |-- Step 2: Track analytics (checkpointed)
    |-- Step 3: Send confirmation email (checkpointed)
    |-- Step 4: Send admin notification (checkpointed)
    |-- Step 5: Grant GitHub access (checkpointed)
    |-- Step 6: Track GitHub access (checkpointed)
    |-- Step 7: Update purchase record (checkpointed)
    |-- Step 8: Send repo access email (checkpointed)
    |-- Step 9: Schedule follow-up sequence (checkpointed)
</code></pre>
<p>Each step wrapped in <code>step.run()</code> is a durable checkpoint. If step 5 fails:</p>
<ul>
<li><p>Steps 1 through 4 do <strong>not</strong> re-run. Their results are cached.</p>
</li>
<li><p>Step 5 retries independently, with its own retry counter.</p>
</li>
<li><p>Once step 5 succeeds, steps 6 through 9 continue.</p>
</li>
</ul>
<p>This is what "durable execution" means. The function's progress survives failures. You get step-level retries instead of function-level retries. No duplicate emails. No duplicate database writes. No partial completion.</p>
<p>I use <a href="https://www.inngest.com/">Inngest</a> for this. It's an event-driven durable execution platform that provides step-level checkpointing out of the box. You define functions with <code>step.run()</code> blocks, and Inngest handles retry logic, state persistence, and observability. No Redis, no worker processes, no custom retry code.</p>
<p>Other tools can achieve similar results (Temporal, for example), but Inngest's developer experience with TypeScript is what sold me. You write normal async functions. The <code>step.run()</code> wrapper is the only addition.</p>
<h2 id="heading-how-to-set-up-the-webhook-endpoint">How to Set Up the Webhook Endpoint</h2>
<p>Your webhook endpoint should be minimal. Validate the signature, extract the event data, send it to your background job system, and return a 200 immediately.</p>
<p>Here's the real webhook endpoint from my production codebase:</p>
<pre><code class="language-typescript">import { constructWebhookEvent } from "@/lib/payments";
import { inngest } from "@/lib/jobs";

app.post("/api/payments/webhook", async ({ request, set }) =&gt; {
  const body = await request.text();
  const sig = request.headers.get("stripe-signature");

  if (!sig) {
    set.status = 400;
    return { error: "Missing signature" };
  }

  try {
    const event = await constructWebhookEvent(body, sig);
    console.log(`[Webhook] Received ${event.type}`);

    if (event.type === "charge.refunded") {
      const charge = event.data.object;
      await inngest.send({
        name: "stripe/charge.refunded",
        data: {
          chargeId: charge.id,
          paymentIntentId: charge.payment_intent,
          amountRefunded: charge.amount_refunded,
          originalAmount: charge.amount,
          currency: charge.currency,
        },
      });
    }

    if (event.type === "checkout.session.expired") {
      const session = event.data.object;
      await inngest.send({
        name: "stripe/checkout.session.expired",
        data: {
          sessionId: session.id,
          customerEmail: session.customer_email,
        },
      });
    }

    return { received: true };
  } catch (error) {
    console.error("[Webhook] Stripe verification failed:", error);
    set.status = 400;
    return { error: "Webhook verification failed" };
  }
});
</code></pre>
<p>Notice what this handler does <strong>not</strong> do: it does not look up users, write to the database, send emails, or call external APIs. It validates the Stripe signature, extracts the relevant fields, and sends a typed event to Inngest. The entire handler completes in milliseconds.</p>
<p>The <code>constructWebhookEvent</code> function wraps Stripe's signature verification:</p>
<pre><code class="language-typescript">import Stripe from "stripe";

export async function constructWebhookEvent(
  payload: string | Buffer,
  signature: string
) {
  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
  if (!webhookSecret) {
    throw new Error("STRIPE_WEBHOOK_SECRET is not set");
  }
  const client = new Stripe(process.env.STRIPE_SECRET_KEY);
  return client.webhooks.constructEventAsync(payload, signature, webhookSecret);
}
</code></pre>
<p>One critical detail: you must pass the <strong>raw request body</strong> (as a string or buffer) to Stripe's signature verification. If your framework parses the body as JSON before you can access the raw string, the signature check will fail. This is the number one cause of "webhook signature verification failed" errors.</p>
<p>The Inngest client setup is minimal:</p>
<pre><code class="language-typescript">import { Inngest } from "inngest";

export const inngest = new Inngest({
  id: "my-app",
});
</code></pre>
<p>For the purchase flow specifically, a different endpoint sends the event (the "claim" route that the frontend calls after the customer returns from Stripe checkout). But the principle is identical: validate, enqueue, return.</p>
<pre><code class="language-typescript">// After verifying payment status with Stripe
await inngest.send({
  name: "purchase/completed",
  data: {
    userId: session.user.id,
    tier,
    sessionId,
  },
});
</code></pre>
<h2 id="heading-how-to-build-a-durable-purchase-flow">How to Build a Durable Purchase Flow</h2>
<p>This is the core of the article. The <code>handlePurchaseCompleted</code> function processes a purchase after payment using 9 individually checkpointed steps. Every step is real production code.</p>
<p>The example below grants access to a private GitHub repository because that's what this particular product sells.</p>
<p>Your product's "grant access" step will be different: upgrading a user to a Pro membership, provisioning API credits, unlocking a course, or activating a subscription. The durable step pattern is the same regardless of what you're delivering.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69a694d8d4dc9b42434c218f/935ca377-52ff-4fc2-8e97-98fb7712c896.png" alt="Durable purchase flow with 9 numbered steps, showing step 5 failing and retrying while steps 1 through 4 remain checkpointed" style="display:block;margin:0 auto" width="5504" height="3072" loading="lazy">

<p>If step 5 fails (for example, the email provider is down), Inngest retries only step 5. Steps 1 through 4 are already checkpointed and don't re-execute. Steps 6 through 9 wait until step 5 succeeds.</p>
<pre><code class="language-typescript">import { eq } from "drizzle-orm";
import { createElement } from "react";

import { inngest } from "@/lib/jobs/client";
import { trackServerEvent } from "@/lib/analytics/server";
import { brand } from "@/lib/brand";
import { db, purchases, users } from "@/lib/db";
import {
  sendEmail,
  PurchaseConfirmationEmail,
  AdminPurchaseNotificationEmail,
  RepoAccessGrantedEmail,
} from "@/lib/email";
import { addCollaborator } from "@/lib/github";

export const handlePurchaseCompleted = inngest.createFunction(
  { id: "purchase-completed", triggers: [{ event: "purchase/completed" }] },
  async ({ event, step }) =&gt; {
    const { userId, tier, sessionId } = event.data;

    // Step 1: Look up user and purchase details
    const { user, purchase } = await step.run(
      "lookup-user-and-purchase",
      async () =&gt; {
        const userResult = await db
          .select({
            id: users.id,
            email: users.email,
            name: users.name,
            githubUsername: users.githubUsername,
          })
          .from(users)
          .where(eq(users.id, userId))
          .limit(1);

        const foundUser = userResult[0];
        if (!foundUser) {
          throw new Error(`User not found: ${userId}`);
        }

        const purchaseResult = await db
          .select({
            amount: purchases.amount,
            currency: purchases.currency,
            stripePaymentIntentId: purchases.stripePaymentIntentId,
          })
          .from(purchases)
          .where(eq(purchases.stripeCheckoutSessionId, sessionId))
          .limit(1);

        const foundPurchase = purchaseResult[0];

        return {
          user: foundUser,
          purchase: foundPurchase ?? {
            amount: 0,
            currency: "usd",
            stripePaymentIntentId: null,
          },
        };
      }
    );

    // Step 2: Track purchase completion in analytics
    await step.run("track-purchase-to-posthog", async () =&gt; {
      await trackServerEvent(userId, "purchase_completed_server", {
        tier,
        amount_cents: purchase.amount,
        currency: purchase.currency,
        stripe_session_id: sessionId,
      });
    });

    // Step 3: Send purchase confirmation to customer
    await step.run("send-purchase-confirmation", async () =&gt; {
      await sendEmail({
        to: user.email,
        subject: `Your purchase is confirmed!`,
        template: createElement(PurchaseConfirmationEmail, {
          amount: purchase.amount,
          currency: purchase.currency,
          customerEmail: user.email,
        }),
      });
    });

    // Step 4: Send admin notification
    await step.run("send-admin-notification", async () =&gt; {
      const adminEmail = process.env.ADMIN_EMAIL;
      if (!adminEmail) return;

      await sendEmail({
        to: adminEmail,
        subject: `New sale: ${user.email}`,
        template: createElement(AdminPurchaseNotificationEmail, {
          amount: purchase.amount,
          currency: purchase.currency,
          customerEmail: user.email,
          customerName: user.name,
          stripeSessionId: purchase.stripePaymentIntentId ?? sessionId,
        }),
      });
    });

    // Early return if user has no GitHub username
    if (!user.githubUsername) {
      return { success: true, userId, tier, githubAccessGranted: false };
    }

    // Step 5: Grant GitHub repository access
    const collaboratorResult = await step.run(
      "add-github-collaborator",
      async () =&gt; {
        return addCollaborator(user.githubUsername!);
      }
    );

    // Step 6: Track GitHub access granted
    await step.run("track-github-access", async () =&gt; {
      await trackServerEvent(userId, "github_access_granted", {
        tier,
        github_username: user.githubUsername,
        invitation_status: collaboratorResult.status,
      });
    });

    // Step 7: Update purchase record
    await step.run("update-purchase-record", async () =&gt; {
      await db
        .update(purchases)
        .set({
          githubAccessGranted: true,
          githubInvitationId: collaboratorResult.status,
          updatedAt: new Date(),
        })
        .where(eq(purchases.stripeCheckoutSessionId, sessionId));
    });

    // Step 8: Send repo access email
    await step.run("send-repo-access-email", async () =&gt; {
      await sendEmail({
        to: user.email,
        subject: `Your repository access is ready!`,
        template: createElement(RepoAccessGrantedEmail, {
          repoUrl: "https://github.com/your-org/your-repo",
        }),
      });
    });

    // Step 9: Schedule follow-up email sequence
    await step.run("schedule-follow-up", async () =&gt; {
      const purchaseRecord = await db
        .select({ id: purchases.id })
        .from(purchases)
        .where(eq(purchases.stripeCheckoutSessionId, sessionId))
        .limit(1);

      if (purchaseRecord[0]) {
        await inngest.send({
          name: "purchase/follow-up.scheduled",
          data: {
            userId,
            purchaseId: purchaseRecord[0].id,
            tier,
          },
        });
      }
    });

    return { success: true, userId, tier, githubAccessGranted: true };
  }
);
</code></pre>
<p>That's a lot of code. Let me walk through each step and explain why it's a separate checkpoint.</p>
<h3 id="heading-step-1-look-up-user-and-purchase">Step 1: Look Up User and Purchase</h3>
<pre><code class="language-typescript">const { user, purchase } = await step.run(
  "lookup-user-and-purchase",
  async () =&gt; {
    // ... database queries ...
    return { user: foundUser, purchase: foundPurchase };
  }
);
</code></pre>
<p>This step queries the database for the user and purchase records. If the database is temporarily unreachable, this step retries on its own.</p>
<p>The return value (<code>user</code> and <code>purchase</code>) is cached by Inngest. Every subsequent step can use <code>user.email</code>, <code>user.githubUsername</code>, and <code>purchase.amount</code> without re-querying the database.</p>
<p>If this step fails permanently (the user doesn't exist), it throws an error that halts the entire function. This is intentional. There's no point continuing if you can't find the user.</p>
<h3 id="heading-step-2-track-analytics">Step 2: Track Analytics</h3>
<pre><code class="language-typescript">await step.run("track-purchase-to-posthog", async () =&gt; {
  await trackServerEvent(userId, "purchase_completed_server", {
    tier,
    amount_cents: purchase.amount,
  });
});
</code></pre>
<p>Analytics tracking is a separate step because analytics services have their own failure modes (rate limits, outages, network timeouts). If PostHog is down, you don't want it to block the confirmation email.</p>
<p>In the production code, this step wraps the call in a try-catch so that a tracking failure doesn't halt the entire function. The analytics event is "nice to have," not critical.</p>
<h3 id="heading-step-3-send-purchase-confirmation-email">Step 3: Send Purchase Confirmation Email</h3>
<pre><code class="language-typescript">await step.run("send-purchase-confirmation", async () =&gt; {
  await sendEmail({
    to: user.email,
    subject: `Your purchase is confirmed!`,
    template: createElement(PurchaseConfirmationEmail, {
      amount: purchase.amount,
      currency: purchase.currency,
      customerEmail: user.email,
    }),
  });
});
</code></pre>
<p>This is the customer-facing confirmation. It's a separate step from the admin notification (step 4) because they're independent operations. If the admin email fails, the customer should still get their confirmation.</p>
<p>The <code>sendEmail</code> function uses Resend under the hood. If Resend returns a 500, this step retries. Because step 2 (analytics) already completed and is checkpointed, it won't re-run.</p>
<h3 id="heading-step-4-send-admin-notification">Step 4: Send Admin Notification</h3>
<pre><code class="language-typescript">await step.run("send-admin-notification", async () =&gt; {
  const adminEmail = process.env.ADMIN_EMAIL;
  if (!adminEmail) return;

  await sendEmail({
    to: adminEmail,
    subject: `New sale: ${user.email}`,
    template: createElement(AdminPurchaseNotificationEmail, { /* ... */ }),
  });
});
</code></pre>
<p>Admin notifications are completely independent from customer-facing operations. Separating them means a failure in one doesn't affect the other.</p>
<h3 id="heading-step-5-grant-github-access">Step 5: Grant GitHub Access</h3>
<pre><code class="language-typescript">const collaboratorResult = await step.run(
  "add-github-collaborator",
  async () =&gt; {
    return addCollaborator(user.githubUsername!);
  }
);
</code></pre>
<p>This is the step most likely to fail. GitHub's API has rate limits: it can time out, and the user's GitHub username might be invalid.</p>
<p>By making this its own step, a GitHub API failure doesn't trigger re-sends of the confirmation email (step 3) or the admin notification (step 4). Those steps are already checkpointed.</p>
<p>Notice the early return before this step: if the user has no GitHub username, the function returns early after step 4. The remaining steps only run when there's a GitHub account to grant access to.</p>
<h3 id="heading-step-6-track-github-access">Step 6: Track GitHub Access</h3>
<pre><code class="language-typescript">await step.run("track-github-access", async () =&gt; {
  await trackServerEvent(userId, "github_access_granted", {
    tier,
    github_username: user.githubUsername,
    invitation_status: collaboratorResult.status,
  });
});
</code></pre>
<p>This uses the <code>collaboratorResult</code> from step 5. Because <code>step.run()</code> caches return values, <code>collaboratorResult.status</code> is available here even if the function was interrupted and resumed between steps 5 and 6.</p>
<h3 id="heading-step-7-update-purchase-record">Step 7: Update Purchase Record</h3>
<pre><code class="language-typescript">await step.run("update-purchase-record", async () =&gt; {
  await db
    .update(purchases)
    .set({
      githubAccessGranted: true,
      githubInvitationId: collaboratorResult.status,
      updatedAt: new Date(),
    })
    .where(eq(purchases.stripeCheckoutSessionId, sessionId));
});
</code></pre>
<p>The database update happens after GitHub access is confirmed. You only mark <code>githubAccessGranted: true</code> after the collaborator invitation actually succeeded.</p>
<p>If you updated the record before granting access and the GitHub step failed, your database would say access was granted when it was not.</p>
<h3 id="heading-step-8-send-repo-access-email">Step 8: Send Repo Access Email</h3>
<pre><code class="language-typescript">await step.run("send-repo-access-email", async () =&gt; {
  await sendEmail({
    to: user.email,
    subject: `Your repository access is ready!`,
    template: createElement(RepoAccessGrantedEmail, {
      repoUrl: "https://github.com/your-org/your-repo",
    }),
  });
});
</code></pre>
<p>This email only sends after the GitHub invitation is confirmed (step 5) and the database is updated (step 7). The ordering matters. You don't want to tell the customer "your access is ready" if the invitation hasn't been sent.</p>
<h3 id="heading-step-9-schedule-follow-up-sequence">Step 9: Schedule Follow-Up Sequence</h3>
<pre><code class="language-typescript">await step.run("schedule-follow-up", async () =&gt; {
  const purchaseRecord = await db
    .select({ id: purchases.id })
    .from(purchases)
    .where(eq(purchases.stripeCheckoutSessionId, sessionId))
    .limit(1);

  if (purchaseRecord[0]) {
    await inngest.send({
      name: "purchase/follow-up.scheduled",
      data: {
        userId,
        purchaseId: purchaseRecord[0].id,
        tier,
      },
    });
  }
});
</code></pre>
<p>The final step triggers a separate Inngest function that handles the follow-up email sequence (day 7 onboarding tips, day 14 feedback request, day 30 testimonial request). This is an event-driven chain: one function completes and triggers another.</p>
<p>The follow-up function uses <code>step.sleep()</code> to wait between emails:</p>
<pre><code class="language-typescript">export const handlePurchaseFollowUp = inngest.createFunction(
  {
    id: "purchase-follow-up",
    triggers: [{ event: "purchase/follow-up.scheduled" }],
    cancelOn: [
      {
        event: "purchase/follow-up.cancelled",
        match: "data.purchaseId",
      },
    ],
  },
  async ({ event, step }) =&gt; {
    const { userId, purchaseId } = event.data;

    await step.sleep("wait-7-days", "7d");

    await step.run("send-day-7-email", async () =&gt; {
      // Check eligibility (user exists, not unsubscribed, not refunded)
      // Send onboarding tips email
    });

    await step.sleep("wait-14-days", "7d");

    await step.run("send-day-14-email", async () =&gt; {
      // Send feedback request email
    });

    await step.sleep("wait-30-days", "16d");

    await step.run("send-day-30-email", async () =&gt; {
      // Send testimonial request email
    });
  }
);
</code></pre>
<p>Notice the <code>cancelOn</code> option. If the purchase is refunded, you can send a <code>purchase/follow-up.cancelled</code> event, and the entire follow-up sequence stops. No stale emails sent to customers who asked for a refund.</p>
<h3 id="heading-why-each-step-must-be-separate">Why Each Step Must Be Separate</h3>
<p>The rule is simple: <strong>any operation that calls an external service or could fail independently should be its own step.</strong></p>
<p>A database query is a step because the database can be temporarily unreachable. An email send is a step because the email provider can return a 500. A GitHub API call is a step because it can be rate-limited.</p>
<p>If two operations always succeed or fail together (they share a single external call), they can be in the same step. But when in doubt, make it a separate step. The overhead is negligible, and the reliability gain is significant.</p>
<h2 id="heading-how-to-handle-refunds-with-the-same-pattern">How to Handle Refunds with the Same Pattern</h2>
<p>The refund flow follows the exact same durable step pattern. This function lives in the same file as <code>handlePurchaseCompleted</code>, so it shares the same imports (plus <code>removeCollaborator</code> from <code>@/lib/github</code> and the refund-specific email templates). Here's the <code>handleRefund</code> function:</p>
<pre><code class="language-typescript">export const handleRefund = inngest.createFunction(
  { id: "refund-processed", triggers: [{ event: "stripe/charge.refunded" }] },
  async ({ event, step }) =&gt; {
    const {
      chargeId,
      paymentIntentId,
      amountRefunded,
      originalAmount,
      currency,
    } = event.data;

    const isFullRefund = amountRefunded &gt;= originalAmount;

    // Step 1: Look up the purchase and user
    const { user, purchase } = await step.run(
      "lookup-purchase-by-payment-intent",
      async () =&gt; {
        const purchaseResult = await db
          .select({
            id: purchases.id,
            userId: purchases.userId,
            stripePaymentIntentId: purchases.stripePaymentIntentId,
            githubAccessGranted: purchases.githubAccessGranted,
          })
          .from(purchases)
          .where(eq(purchases.stripePaymentIntentId, paymentIntentId))
          .limit(1);

        const foundPurchase = purchaseResult[0];
        if (!foundPurchase) {
          return { user: null, purchase: null };
        }

        const userResult = await db
          .select({
            id: users.id,
            email: users.email,
            name: users.name,
            githubUsername: users.githubUsername,
          })
          .from(users)
          .where(eq(users.id, foundPurchase.userId))
          .limit(1);

        return { user: userResult[0] ?? null, purchase: foundPurchase };
      }
    );

    if (!purchase || !user) {
      return { success: false, reason: "no_matching_purchase" };
    }

    let accessRevoked = false;

    // Step 2: Revoke GitHub access (only for full refunds)
    if (isFullRefund &amp;&amp; user.githubUsername &amp;&amp; purchase.githubAccessGranted) {
      const revokeResult = await step.run(
        "revoke-github-access",
        async () =&gt; {
          return removeCollaborator(user.githubUsername!);
        }
      );
      accessRevoked = revokeResult.success;
    }

    // Step 3: Update purchase status
    await step.run("update-purchase-status", async () =&gt; {
      if (isFullRefund) {
        await db
          .update(purchases)
          .set({
            status: "refunded",
            githubAccessGranted: false,
            updatedAt: new Date(),
          })
          .where(eq(purchases.id, purchase.id));
      } else {
        await db
          .update(purchases)
          .set({
            status: "partially_refunded",
            updatedAt: new Date(),
          })
          .where(eq(purchases.id, purchase.id));
      }
    });

    // Step 4: Track refund in analytics
    await step.run("track-refund-event", async () =&gt; {
      await trackServerEvent(user.id, "refund_processed", {
        charge_id: chargeId,
        amount_cents: amountRefunded,
        original_amount_cents: originalAmount,
        currency,
        is_full_refund: isFullRefund,
        github_access_revoked: accessRevoked,
      });
    });

    // Step 5: Notify customer
    await step.run("send-customer-notification", async () =&gt; {
      if (isFullRefund) {
        await sendEmail({
          to: user.email,
          subject: "Your refund has been processed",
          template: createElement(AccessRevokedEmail, {
            customerEmail: user.email,
            refundAmount: amountRefunded,
            currency,
          }),
        });
      } else {
        await sendEmail({
          to: user.email,
          subject: "Your partial refund has been processed",
          template: createElement(PartialRefundEmail, {
            customerEmail: user.email,
            refundAmount: amountRefunded,
            originalAmount,
            currency,
          }),
        });
      }
    });

    // Step 6: Notify admin
    await step.run("send-admin-notification", async () =&gt; {
      const adminEmail = process.env.ADMIN_EMAIL;
      if (!adminEmail) return;

      await sendEmail({
        to: adminEmail,
        subject: `\({isFullRefund ? "Full" : "Partial"} refund: \){user.email}`,
        template: createElement(AdminRefundNotificationEmail, {
          customerEmail: user.email,
          customerName: user.name,
          githubUsername: user.githubUsername,
          refundAmount: amountRefunded,
          originalAmount,
          currency,
          stripeChargeId: chargeId,
          accessRevoked,
          isPartialRefund: !isFullRefund,
        }),
      });
    });

    return { success: true, accessRevoked, isFullRefund, userId: user.id };
  }
);
</code></pre>
<p>Three things are worth calling out in the refund flow.</p>
<ol>
<li><p><strong>Partial versus full refunds:</strong> The function distinguishes between the two using a simple comparison: <code>amountRefunded &gt;= originalAmount</code>. For a partial refund, the customer keeps access but the purchase status changes to <code>partially_refunded</code>. For a full refund, GitHub access is revoked and the status becomes <code>refunded</code>.  </p>
<p>This matters for your database integrity. Downstream systems (your dashboard, your analytics, your support tools) need accurate status values.</p>
</li>
<li><p><strong>Conditional step execution:</strong> The "revoke GitHub access" step only runs if three conditions are true: it's a full refund, the user has a GitHub username, and access was previously granted. Inngest handles this cleanly by skipping steps that don't need to run.  </p>
<p>This is more readable than deeply nested if-else blocks in a monolithic handler.</p>
</li>
<li><p><strong>Separate notifications for customers and admins:</strong> The customer gets a different email depending on whether the refund is full or partial. The admin always gets a detailed notification including the charge ID, the customer's GitHub username, and whether access was revoked.</p>
</li>
</ol>
<p>These are separate steps because a failure in the admin notification shouldn't block the customer notification. The customer's email is the higher priority.</p>
<h2 id="heading-how-to-recover-abandoned-checkouts">How to Recover Abandoned Checkouts</h2>
<p>Abandoned cart recovery is where the <code>step.sleep()</code> method shines. When a Stripe checkout session expires, you want to send a recovery email. But not immediately.</p>
<p>You want to wait an hour or so, giving the customer time to return on their own.</p>
<pre><code class="language-typescript">export const handleCheckoutExpired = inngest.createFunction(
  {
    id: "checkout-expired",
    triggers: [{ event: "stripe/checkout.session.expired" }],
  },
  async ({ event, step }) =&gt; {
    const { customerEmail, sessionId } = event.data;

    if (!customerEmail) {
      return { success: false, reason: "no_email" };
    }

    // Wait 1 hour before sending recovery email
    await step.sleep("wait-before-recovery-email", "1h");

    // Send abandoned cart email
    await step.run("send-abandoned-cart-email", async () =&gt; {
      const checkoutUrl = `https://yoursite.com/pricing`;

      await sendEmail({
        to: customerEmail,
        subject: "Your checkout is waiting",
        template: createElement(AbandonedCartEmail, {
          customerEmail,
          checkoutUrl,
        }),
      });
    });

    // Track the event
    await step.run("track-abandoned-cart", async () =&gt; {
      await trackServerEvent("anonymous", "abandoned_cart_email_sent", {
        customer_email: customerEmail,
        session_id: sessionId,
      });
    });

    return { success: true, customerEmail };
  }
);
</code></pre>
<p>The <code>step.sleep("wait-before-recovery-email", "1h")</code> line is the key. This pauses the function for one hour without consuming any compute resources.</p>
<p>Inngest handles the scheduling internally. After one hour, the function resumes and sends the email.</p>
<p>Without durable execution, you would need a cron job that queries a database for expired sessions, or a delayed job queue with Redis, or a <code>setTimeout</code> that gets lost when your server restarts. The <code>step.sleep()</code> approach is simpler, more readable, and more reliable.</p>
<p>There's also a guard at the top of the function. If Stripe doesn't have a customer email for the session (the customer closed the checkout before entering their email), the function returns early. There's no point scheduling a recovery email with no address to send it to.</p>
<p>This pattern scales to more complex recovery flows. You could add a second <code>step.sleep()</code> and send a follow-up recovery email three days later if the customer still hasn't purchased. You could check if the customer has since completed a purchase (by querying the database in a <code>step.run()</code>) and skip the email if they have.</p>
<p>Each additional step is one more <code>step.run()</code> or <code>step.sleep()</code> call. The function reads like a script describing your business logic, not a tangle of cron jobs and database flags.</p>
<h2 id="heading-how-to-test-webhook-handlers-locally">How to Test Webhook Handlers Locally</h2>
<p>Local testing is one of the biggest pain points with Stripe webhooks. You need Stripe to send events to your local machine, and you need your background job system running to process them. Here's the setup.</p>
<h3 id="heading-how-to-forward-stripe-events-locally">How to Forward Stripe Events Locally</h3>
<p>Install the <a href="https://stripe.com/docs/stripe-cli">Stripe CLI</a> and forward webhook events to your local server:</p>
<pre><code class="language-bash">stripe listen --forward-to localhost:3000/api/payments/webhook
</code></pre>
<p>The CLI prints a webhook signing secret (starting with <code>whsec_</code>). Set this as your <code>STRIPE_WEBHOOK_SECRET</code> environment variable for local development.</p>
<p>You can trigger test events directly:</p>
<pre><code class="language-bash">stripe trigger checkout.session.completed
stripe trigger charge.refunded
stripe trigger checkout.session.expired
</code></pre>
<h3 id="heading-how-to-run-the-inngest-dev-server">How to Run the Inngest Dev Server</h3>
<p>Inngest provides a local dev server that shows you every function execution, every step, and every retry in real time:</p>
<pre><code class="language-bash">npx inngest-cli@latest dev -u http://localhost:3000/api/inngest
</code></pre>
<p>The <code>-u</code> flag tells the Inngest dev server where your application is running so it can discover your functions. Open <code>http://localhost:8288</code> in your browser to see the Inngest dashboard.</p>
<h3 id="heading-how-to-watch-step-execution">How to Watch Step Execution</h3>
<p>The Inngest dev dashboard is where the durable execution pattern really clicks. When you trigger a Stripe event, you can see:</p>
<ol>
<li><p>The event arriving in the "Events" tab.</p>
</li>
<li><p>The function triggering in the "Runs" tab.</p>
</li>
<li><p>Each step executing one by one, with its input, output, and duration.</p>
</li>
<li><p>If a step fails, you see the error and the retry attempt.</p>
</li>
</ol>
<p>This visibility is something you don't get with inline webhook handlers. When a customer reports "I paid but didn't get access," you can look up the function run in the Inngest dashboard and see exactly which step failed and why. That kind of observability is invaluable in production.</p>
<h3 id="heading-how-to-simulate-failures">How to Simulate Failures</h3>
<p>To test the retry behavior, you can intentionally make a step fail. For example, temporarily throw an error in the "add-github-collaborator" step:</p>
<pre><code class="language-typescript">const collaboratorResult = await step.run(
  "add-github-collaborator",
  async () =&gt; {
    throw new Error("Simulated GitHub API failure");
  }
);
</code></pre>
<p>In the Inngest dashboard, you'll see:</p>
<ul>
<li><p>Steps 1 through 4 succeed and their results are cached.</p>
</li>
<li><p>Step 5 fails and is retried according to the retry policy.</p>
</li>
<li><p>Steps 6 through 9 remain pending until step 5 succeeds.</p>
</li>
</ul>
<p>Remove the thrown error, and on the next retry, step 5 succeeds. Steps 6 through 9 then execute in sequence, while steps 1 through 4 aren't re-executed. This is the checkpoint behavior in action.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The pattern for reliable Stripe webhooks comes down to one principle: <strong>separate receiving from processing.</strong></p>
<p>Your webhook endpoint validates the Stripe signature and sends a typed event to a background job system. That's all it does. The processing happens in a durable function where each step is individually checkpointed and retried.</p>
<p>Here's what this gives you:</p>
<ul>
<li><p><strong>No duplicate emails:</strong> A step that already succeeded doesn't re-run.</p>
</li>
<li><p><strong>No partial state:</strong> If step 5 fails, steps 1 through 4 are preserved and step 5 retries independently.</p>
</li>
<li><p><strong>Full observability:</strong> You can see exactly which step failed and why, for every function run.</p>
</li>
<li><p><strong>Built-in delayed execution:</strong> <code>step.sleep()</code> handles recovery emails and follow-up sequences without cron jobs.</p>
</li>
<li><p><strong>Composable workflows:</strong> One function can trigger another via events, creating chains like purchase completion leading to a 30-day follow-up sequence.</p>
</li>
</ul>
<p>This pattern isn't limited to Stripe. Any multi-step webhook processing benefits from durable execution: GitHub webhooks that trigger CI pipelines, Resend webhooks that track email delivery, or calendar webhooks that sync across services.</p>
<p>The principle is the same: Validate. Enqueue. Process durably.</p>
<p>I've used this pattern in production for <a href="https://eden-stack.com?utm_source=freecodecamp&amp;utm_medium=article&amp;utm_campaign=stripe-webhooks-background-jobs">Eden Stack</a>, where the purchase flow handles everything from payment confirmation to GitHub repository access grants to multi-week email sequences. The 9-step purchase function has processed every payment without a single missed step or duplicate email.</p>
<p>If you're building a SaaS with Stripe, start with the webhook endpoint pattern from this article. Keep the endpoint thin and move the processing into durable steps. You'll save yourself from the 3 AM debugging session when a customer says "I paid but nothing happened."</p>
<p>If you want the complete Stripe webhook and Inngest integration pre-built with purchase flows, refund handling, and follow-up email sequences ready to go, <a href="https://eden-stack.com?utm_source=freecodecamp&amp;utm_medium=article&amp;utm_campaign=stripe-webhooks-background-jobs">Eden Stack</a> includes everything from this article alongside 30+ additional production-tested patterns.</p>
<p><em>Magnus Rodseth builds AI-native applications and is the creator of</em> <a href="https://eden-stack.com?utm_source=freecodecamp&amp;utm_medium=article&amp;utm_campaign=stripe-webhooks-background-jobs"><em>Eden Stack</em></a><em>, a production-ready starter kit with 30+ Claude skills encoding production patterns for AI-native SaaS development.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Implement Token Bucket Rate Limiting with FastAPI ]]>
                </title>
                <description>
                    <![CDATA[ APIs power everything from mobile apps to enterprise platforms, quietly handling millions of requests per day. Without safeguards, a single misconfigured client or a burst of automated traffic can ove ]]>
                </description>
                <link>https://www.freecodecamp.org/news/token-bucket-rate-limiting-fastapi/</link>
                <guid isPermaLink="false">69c6f8747cf270651055571c</guid>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ratelimit ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Prosper Ugbovo ]]>
                </dc:creator>
                <pubDate>Fri, 27 Mar 2026 21:36:52 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/fba3d4a6-faca-429a-8e16-a3e9778d2cf8.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>APIs power everything from mobile apps to enterprise platforms, quietly handling millions of requests per day. Without safeguards, a single misconfigured client or a burst of automated traffic can overwhelm your service, degrading performance for everyone.</p>
<p>Rate limiting prevents this. It controls how many requests a client can make within a given timeframe, protecting your infrastructure from both intentional abuse and accidental overload.</p>
<p>Among the several algorithms used for rate limiting, the <strong>Token Bucket</strong> stands out for its balance of simplicity and flexibility. Unlike fixed window counters that reset abruptly, the Token Bucket allows short bursts of traffic while still enforcing a sustainable long-term rate. This makes it a practical choice for APIs where clients occasionally need to send a quick flurry of requests without being penalized.</p>
<p>In this guide, you'll implement a Token Bucket rate limiter in a FastAPI application. You'll build the algorithm from scratch as a Python class, wire it into FastAPI as middleware with per-user tracking, add standard rate limit headers to your responses, and test everything with a simple script. By the end, you'll have a working rate limiter you can drop into any FastAPI project.</p>
<h3 id="heading-what-well-cover">What we'll cover:</h3>
<ol>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-understanding-the-token-bucket-algorithm">Understanding the Token Bucket Algorithm</a></p>
</li>
<li><p><a href="#heading-setting-up-the-fastapi-project">Setting Up the FastAPI Project</a></p>
</li>
<li><p><a href="#heading-implementing-the-token-bucket-class">Implementing the Token Bucket Class</a></p>
</li>
<li><p><a href="#heading-adding-peruser-rate-limiting-middleware">Adding Per-User Rate Limiting Middleware</a></p>
</li>
<li><p><a href="#heading-testing-the-rate-limiter">Testing the Rate Limiter</a></p>
</li>
<li><p><a href="#heading-where-rate-limiting-fits-in-your-architecture">Where Rate Limiting Fits in Your Architecture</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow this tutorial, you'll need:</p>
<ul>
<li><p><strong>Python 3.9 or later</strong> installed on your machine. You can verify your version by running <code>python --version</code>.</p>
</li>
<li><p><strong>Familiarity with Python</strong> and basic knowledge of how HTTP APIs work.</p>
</li>
<li><p><strong>A text editor</strong> such as VS Code, Vim, or any editor you prefer.</p>
</li>
</ul>
<h2 id="heading-understanding-the-token-bucket-algorithm">Understanding the Token Bucket Algorithm</h2>
<p>Before writing code, it helps to understand the mechanism you'll be building.</p>
<p>The Token Bucket algorithm models rate limiting with two simple concepts: a <strong>bucket</strong> that holds tokens, and a <strong>refill process</strong> that adds tokens at a steady rate.</p>
<p>Here is how it works:</p>
<ol>
<li><p>The bucket starts full, holding a fixed maximum number of tokens (the capacity).</p>
</li>
<li><p>Each incoming request costs one token. If the bucket has tokens available, the request is allowed, and one token is removed.</p>
</li>
<li><p>If the bucket is empty, the request is rejected with a <code>429 Too Many Requests</code> response.</p>
</li>
<li><p>Tokens are added back to the bucket at a constant refill rate, regardless of whether requests are coming in. The bucket never exceeds its maximum capacity.</p>
</li>
</ol>
<p>The capacity determines how large a burst the system absorbs. The refill rate defines the sustained throughput. For example, a bucket with a capacity of 10 and a refill rate of 2 tokens per second allows a client to fire 10 requests instantly, but after that, they can only make 2 requests per second until the bucket refills.</p>
<p>This two-parameter design gives you precise control:</p>
<table>
<thead>
<tr>
<th>Parameter</th>
<th>Controls</th>
<th>Example</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Capacity</strong> (max tokens)</td>
<td>Maximum burst size</td>
<td>10 tokens = 10 requests at once</td>
</tr>
<tr>
<td><strong>Refill rate</strong></td>
<td>Sustained throughput</td>
<td>2 tokens/sec = 2 requests/sec long-term</td>
</tr>
<tr>
<td><strong>Refill interval</strong></td>
<td>Granularity of refill</td>
<td>1.0 sec = tokens added every second</td>
</tr>
</tbody></table>
<p>Compared to other rate-limiting algorithms:</p>
<ul>
<li><p><strong>Fixed Window</strong> counters reset at hard boundaries (for example, every minute), which can allow double the intended rate at window edges. The Token Bucket has no such boundary.</p>
</li>
<li><p><strong>Sliding Window</strong> counters are more accurate but more complex to implement and maintain.</p>
</li>
<li><p><strong>Leaky Bucket</strong> processes requests at a fixed rate and queues the rest. The Token Bucket is similar, but allows bursts instead of forcing a constant pace.</p>
</li>
</ul>
<p>The Token Bucket is widely used in production systems. AWS API Gateway, NGINX, and Stripe all use variations of it.</p>
<h2 id="heading-setting-up-the-fastapi-project">Setting Up the FastAPI Project</h2>
<p>Create a project directory and install the dependencies:</p>
<pre><code class="language-shell">mkdir fastapi-ratelimit &amp;&amp; cd fastapi-ratelimit
</code></pre>
<p>Create and activate a virtual environment:</p>
<pre><code class="language-shell">python -m venv venv
</code></pre>
<p>On Linux/macOS:</p>
<pre><code class="language-shell">source venv/bin/activate
</code></pre>
<p>On Windows:</p>
<pre><code class="language-shell">venv\Scripts\activate
</code></pre>
<p>Install FastAPI and Uvicorn:</p>
<pre><code class="language-shell">pip install fastapi uvicorn
</code></pre>
<p>Create the project file structure:</p>
<pre><code class="language-plaintext">fastapi-ratelimit/
├── main.py
└── ratelimiter.py
</code></pre>
<p>Create <code>main.py</code> with a minimal FastAPI application:</p>
<pre><code class="language-python">from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello, world!"}
</code></pre>
<p>Start the server to verify the setup:</p>
<pre><code class="language-shell">uvicorn main:app --reload
</code></pre>
<p>You should see output similar to:</p>
<pre><code class="language-plaintext">INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process
</code></pre>
<p>Open in your browser <a href="http://127.0.0.1:8000">http://127.0.0.1:8000</a> or run curl <a href="http://127.0.0.1:8000">http://127.0.0.1:8000</a>. You should receive:</p>
<pre><code class="language-json">{"message": "Hello, world!"}
</code></pre>
<p>With the project running, you can move on to building the rate limiter.</p>
<h2 id="heading-implementing-the-token-bucket-class">Implementing the Token Bucket Class</h2>
<p>Open <code>ratelimiter.py</code> in your editor and add the following code. This class implements the Token Bucket algorithm with thread-safe operations:</p>
<pre><code class="language-python">import time
import threading


class TokenBucket:
    """
    Token Bucket rate limiter.

    Each bucket starts full at `max_tokens` and refills `refill_rate`
    tokens every `interval` seconds, up to the maximum capacity.
    """

    def __init__(self, max_tokens: int, refill_rate: int, interval: float):
        """
        Initialize a new Token Bucket.

        :param max_tokens: Maximum number of tokens the bucket can hold (burst capacity).
        :param refill_rate: Number of tokens added per refill interval.
        :param interval: Time in seconds between refills.
        """
        assert max_tokens &gt; 0, "max_tokens must be positive"
        assert refill_rate &gt; 0, "refill_rate must be positive"
        assert interval &gt; 0, "interval must be positive"

        self.max_tokens = max_tokens
        self.refill_rate = refill_rate
        self.interval = interval

        self.tokens = max_tokens
        self.refilled_at = time.time()
        self.lock = threading.Lock()

    def _refill(self):
        """Add tokens based on elapsed time since the last refill."""
        now = time.time()
        elapsed = now - self.refilled_at

        if elapsed &gt;= self.interval:
            num_refills = int(elapsed // self.interval)
            self.tokens = min(
                self.max_tokens,
                self.tokens + num_refills * self.refill_rate
            )
            # Advance the timestamp by the number of full intervals consumed,
            # not to `now`, so partial intervals aren't lost.
            self.refilled_at += num_refills * self.interval

    def allow_request(self, tokens: int = 1) -&gt; bool:
        """
        Attempt to consume `tokens` from the bucket.

        Returns True if the request is allowed, False if the bucket
        does not have enough tokens.
        """
        with self.lock:
            self._refill()

            if self.tokens &gt;= tokens:
                self.tokens -= tokens
                return True
            return False

    def get_remaining(self) -&gt; int:
        """Return the current number of available tokens."""
        with self.lock:
            self._refill()
            return self.tokens

    def get_reset_time(self) -&gt; float:
        """Return the Unix timestamp when the next refill occurs."""
        with self.lock:
            return self.refilled_at + self.interval
</code></pre>
<p>The class has three public methods:</p>
<ul>
<li><p><code>allow_request()</code> is the core method. It refills tokens based on elapsed time, then tries to consume one. It returns <code>True</code> if the request is allowed, <code>False</code> if the bucket is empty.</p>
</li>
<li><p><code>get_remaining()</code> returns the number of tokens the client has left. You will use this for response headers.</p>
</li>
<li><p><code>get_reset_time()</code> returns when the next token will be added. This is also exposed in response headers.</p>
</li>
</ul>
<p>The <code>threading.Lock</code> ensures that concurrent requests don't create race conditions when reading or modifying the token count. This is important because FastAPI runs request handlers concurrently.</p>
<p><strong>Note:</strong> This implementation stores bucket state in memory. If you restart the server, all buckets reset. For persistence across restarts or multiple server instances, you would store token counts in Redis or a similar external store. The in-memory approach is sufficient for single-instance deployments.</p>
<h2 id="heading-adding-per-user-rate-limiting-middleware">Adding Per-User Rate Limiting Middleware</h2>
<p>A single global bucket would throttle all users together. One heavy user could exhaust the limit for everyone. Instead, you'll assign a separate bucket to each user, identified by their IP address.</p>
<p>Add the following to <code>ratelimiter.py</code>, below the <code>TokenBucket</code> class:</p>
<pre><code class="language-python">from collections import defaultdict


class RateLimiterStore:
    """
    Manages per-user Token Buckets.

    Each unique client key (e.g., IP address) gets its own bucket
    with identical parameters.
    """

    def __init__(self, max_tokens: int, refill_rate: int, interval: float):
        self.max_tokens = max_tokens
        self.refill_rate = refill_rate
        self.interval = interval
        self._buckets: dict[str, TokenBucket] = {}
        self._lock = threading.Lock()

    def get_bucket(self, key: str) -&gt; TokenBucket:
        """
        Return the TokenBucket for a given client key.
        Creates a new bucket if one does not exist yet.
        """
        with self._lock:
            if key not in self._buckets:
                self._buckets[key] = TokenBucket(
                    max_tokens=self.max_tokens,
                    refill_rate=self.refill_rate,
                    interval=self.interval,
                )
            return self._buckets[key]
</code></pre>
<p>Now open <code>main.py</code> and replace its contents with the full application, including the rate-limiting middleware:</p>
<pre><code class="language-python">import time

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

from ratelimiter import RateLimiterStore

app = FastAPI()

# Configure rate limits: 10 requests burst, 2 tokens added every 1 second.
limiter = RateLimiterStore(max_tokens=10, refill_rate=2, interval=1.0)


@app.middleware("http")
async def rate_limit_middleware(request: Request, call_next):
    """
    Middleware that enforces per-IP rate limiting on every request.
    Adds standard rate limit headers to every response.
    """
    # Identify the client by IP address.
    client_ip = request.client.host
    bucket = limiter.get_bucket(client_ip)

    # Check if the client has tokens available.
    if not bucket.allow_request():
        retry_after = bucket.get_reset_time() - time.time()
        return JSONResponse(
            status_code=429,
            content={"detail": "Too many requests. Try again later."},
            headers={
                "Retry-After": str(max(1, int(retry_after))),
                "X-RateLimit-Limit": str(bucket.max_tokens),
                "X-RateLimit-Remaining": str(bucket.get_remaining()),
                "X-RateLimit-Reset": str(int(bucket.get_reset_time())),
            },
        )

    # Request is allowed. Process it and add rate limit headers to the response.
    response = await call_next(request)
    response.headers["X-RateLimit-Limit"] = str(bucket.max_tokens)
    response.headers["X-RateLimit-Remaining"] = str(bucket.get_remaining())
    response.headers["X-RateLimit-Reset"] = str(int(bucket.get_reset_time()))
    return response


@app.get("/")
async def root():
    return {"message": "Hello, world!"}


@app.get("/data")
async def get_data():
    return {"data": "Some important information"}


@app.get("/health")
async def health():
    return {"status": "ok"}
</code></pre>
<p>The middleware does the following on every incoming request:</p>
<ol>
<li><p>Extracts the client's IP address from <code>request.client.host</code>.</p>
</li>
<li><p>Retrieves (or creates) that client's Token Bucket from the store.</p>
</li>
<li><p>Calls <code>allow_request()</code>. If the bucket is empty, it returns a <code>429</code> response with a <code>Retry-After</code> header telling the client how long to wait.</p>
</li>
<li><p>If tokens are available, it processes the request normally and attaches rate limit headers to the response.</p>
</li>
</ol>
<p>The three <code>X-RateLimit-*</code> headers follow a <a href="https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/">widely adopted convention</a>:</p>
<table>
<thead>
<tr>
<th>Header</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td><code>X-RateLimit-Limit</code></td>
<td>Maximum burst capacity (max tokens)</td>
</tr>
<tr>
<td><code>X-RateLimit-Remaining</code></td>
<td>Tokens left in the current bucket</td>
</tr>
<tr>
<td><code>X-RateLimit-Reset</code></td>
<td>Unix timestamp when the next refill occurs</td>
</tr>
</tbody></table>
<p>These headers allow well-behaved clients to self-throttle before hitting the limit.</p>
<h2 id="heading-testing-the-rate-limiter">Testing the Rate Limiter</h2>
<p>Restart the server if it's not already running:</p>
<pre><code class="language-shell">uvicorn main:app --reload
</code></pre>
<h3 id="heading-manual-testing-with-curl">Manual Testing with curl</h3>
<p>Manual testing with <code>curl</code> is useful during development when you want to quickly verify that your middleware is working. A single request lets you confirm that the rate limit headers are present, the values are correct, and one token is consumed as expected.</p>
<p>This approach is fast and requires no additional setup, making it ideal for spot-checking your configuration after making changes.</p>
<p>Send a single request and inspect the response:</p>
<pre><code class="language-shell">curl -i http://127.0.0.1:8000/data
</code></pre>
<p>You should see a <code>200</code> response with headers like:</p>
<pre><code class="language-plaintext">HTTP/1.1 200 OK
x-ratelimit-limit: 10
x-ratelimit-remaining: 9
x-ratelimit-reset: 1739836801
</code></pre>
<h3 id="heading-automated-burst-test">Automated Burst Test</h3>
<p>While <code>curl</code> confirms that the rate limiter is active, it can't verify that the limiter actually blocks requests when the bucket is empty. For that, you need to send requests faster than the refill rate and observe the <code>429</code> responses. An automated burst test is essential before deploying to production, after changing your bucket parameters, or when you need to verify both the blocking and refill behavior.</p>
<p>Create a file called <code>test_ratelimit.py</code> in your project directory:</p>
<pre><code class="language-python">import requests
import time


def test_burst():
    """Send 15 rapid requests to trigger the rate limit."""
    url = "http://127.0.0.1:8000/data"
    results = []

    for i in range(15):
        response = requests.get(url)
        remaining = response.headers.get("X-RateLimit-Remaining", "N/A")
        results.append((i + 1, response.status_code, remaining))
        print(f"Request {i+1:2d} | Status: {response.status_code} | Remaining: {remaining}")

    print()

    allowed = sum(1 for _, status, _ in results if status == 200)
    blocked = sum(1 for _, status, _ in results if status == 429)
    print(f"Allowed: {allowed}, Blocked: {blocked}")


def test_refill():
    """Exhaust tokens, wait for a refill, then confirm requests succeed again."""
    url = "http://127.0.0.1:8000/data"

    print("\n--- Exhausting tokens ---")
    for i in range(12):
        response = requests.get(url)
        print(f"Request {i+1:2d} | Status: {response.status_code}")

    print("\n--- Waiting 3 seconds for refill ---")
    time.sleep(3)

    print("\n--- Sending requests after refill ---")
    for i in range(5):
        response = requests.get(url)
        remaining = response.headers.get("X-RateLimit-Remaining", "N/A")
        print(f"Request {i+1:2d} | Status: {response.status_code} | Remaining: {remaining}")


if __name__ == "__main__":
    print("=== Burst Test ===")
    test_burst()

    # Allow bucket to refill before next test
    time.sleep(6)

    print("\n=== Refill Test ===")
    test_refill()
</code></pre>
<p>Install the <code>requests</code> library if you don't have it:</p>
<pre><code class="language-shell">pip install requests
</code></pre>
<p>Run the test:</p>
<pre><code class="language-shell">python test_ratelimit.py
</code></pre>
<p>You should see output similar to:</p>
<pre><code class="language-output">=== Burst Test ===
Request  1 | Status: 200 | Remaining: 9
Request  2 | Status: 200 | Remaining: 8
Request  3 | Status: 200 | Remaining: 7
...
Request 10 | Status: 200 | Remaining: 0
Request 11 | Status: 429 | Remaining: 0
Request 12 | Status: 429 | Remaining: 0
...
Request 15 | Status: 429 | Remaining: 0

Allowed: 10, Blocked: 5
</code></pre>
<p>The first 10 requests succeed (one token each from the full bucket). Requests 11 through 15 are rejected because the bucket is empty. The refill test then confirms that after waiting, tokens reappear and requests succeed again.</p>
<p><strong>Note:</strong> The exact split between allowed and blocked requests may vary slightly due to timing. Tokens may refill between rapid requests. This is expected behavior.</p>
<h2 id="heading-where-rate-limiting-fits-in-your-architecture">Where Rate Limiting Fits in Your Architecture</h2>
<p>The implementation in this tutorial runs inside your application process, which is the simplest approach and works well for single-instance deployments. In larger systems, rate limiting typically appears at multiple layers:</p>
<ul>
<li><p><strong>API gateway level</strong> (NGINX, Kong, Traefik, Envoy): A coarse global rate limit applied to all traffic before it reaches your application. This protects against large-scale abuse and DDoS.</p>
</li>
<li><p><strong>Application level</strong> (this tutorial): Fine-grained per-user or per-endpoint limits inside your service. This is useful for enforcing different quotas on different API tiers.</p>
</li>
<li><p><strong>Both</strong>: Many production systems combine a gateway-level global limiter with an in-app per-user limiter. The gateway catches the flood and the application enforces business rules.</p>
</li>
</ul>
<p>For multi-instance deployments (multiple server processes behind a load balancer), the in-memory <code>RateLimiterStore</code> won't share state across instances. In that case, replace the in-memory dictionary with Redis. The Token Bucket logic stays the same – only the storage layer changes.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this guide, you built a Token Bucket rate limiter from scratch and integrated it into a FastAPI application with per-user tracking and standard rate limit response headers. You also tested the implementation to verify that burst capacity and refill behavior work as expected.</p>
<p>The Token Bucket algorithm gives you two straightforward controls, capacity for burst tolerance and refill rate for sustained throughput, which cover the vast majority of rate-limiting needs.</p>
<p>From here, you can extend this foundation by:</p>
<ul>
<li><p>Replacing the in-memory store with Redis for multi-instance deployments.</p>
</li>
<li><p>Applying different rate limits per endpoint by creating separate <code>RateLimiterStore</code> instances.</p>
</li>
<li><p>Using authenticated user IDs instead of IP addresses for more accurate client identification.</p>
</li>
<li><p>Adding metrics and logging to track how often clients are being throttled.</p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Voice-Powered AI Application with the Web Speech API ]]>
                </title>
                <description>
                    <![CDATA[ The Web Speech API is a web browser API that enables web applications to use sound as data in their operations. With the API, web apps can transcribe the speech in sound input and also synthesise spee ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-voice-powered-ai-application-with-the-web-speech-api/</link>
                <guid isPermaLink="false">69c5a2af10e664c5da34709b</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Thu, 26 Mar 2026 21:18:39 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/d6c77704-8ad6-4852-8a10-6656c76a34f4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API">Web Speech API</a> is a web browser API that enables web applications to use sound as data in their operations. With the API, web apps can transcribe the speech in sound input and also synthesise speech from text.</p>
<p>This guide shows you how to build a full-stack web application that:</p>
<ul>
<li><p>Accepts audio input and transcribes the speech in it</p>
</li>
<li><p>Prompts an AI agent with the transcription</p>
</li>
<li><p>Displays the AI response on the UI</p>
</li>
</ul>
<p>The application you'll build will be a simplified version of the <strong>Use Voice</strong> feature on AI chat applications highlighted in the image below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/7adc60ff-cedb-48bc-a5c9-6e913dd3cc60.png" alt="Use voice feature of AI chat applications" style="display:block;margin:0 auto" width="700" height="390" loading="lazy">

<p>By practising along with this article, you'll learn how to:</p>
<ul>
<li><p>Build a frontend application that uses the <a href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition">SpeechRecognition</a> API to accept voice input and transcribe it</p>
</li>
<li><p>Build a backend app that prompts an AI assistant of your choice and sends a response back to clients</p>
</li>
<li><p>Connect both applications together to send the transcription to the backend as a prompt and display the AI response on the frontend</p>
</li>
</ul>
<p>Optionally, you'll also learn how to host the frontend with Firebase and the backend with Google Cloud Run.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-the-web-speech-api">The Web Speech API</a></p>
<ul>
<li><a href="#heading-how-to-use-the-web-speech-api-in-javascript-for-seo">How to Use the Web Speech API in JavaScript for SEO</a></li>
</ul>
</li>
<li><p><a href="#heading-how-the-application-works">How the Application Works</a></p>
</li>
<li><p><a href="#heading-how-to-build-the-application">How to Build the Application</a></p>
<ul>
<li><p><a href="#heading-create-the-backend-application-with-nodejs">Create the Backend Application with Node.js</a></p>
</li>
<li><p><a href="#heading-integrate-an-ai-assistant-into-the-nodejs-application">Integrate an AI Assistant into the Node.js Application</a></p>
</li>
<li><p><a href="#heading-create-the-frontend-application-with-vite">Create the Frontend Application with Vite</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-test-the-application-locally">Test the Application Locally</a></p>
</li>
<li><p><a href="#heading-deploy-the-backend-application-with-google-cloud-run">Deploy the Backend Application with Google Cloud Run</a></p>
</li>
<li><p><a href="#heading-deploy-the-frontend-application-with-firebase">Deploy the Frontend Application with Firebase</a></p>
</li>
<li><p><a href="#heading-connect-the-deployed-applications">Connect the Deployed Applications</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>This guide assumes that you have a working knowledge of HTML, CSS, and JavaScript in the browser. Basic familiarity with Node.js is beneficial but not essential.</p>
<p>In addition, you should have:</p>
<ul>
<li><p>Google Chrome (at least version 33 ) and a functional audio input device</p>
</li>
<li><p><a href="https://nodejs.org/">Node.js</a>&nbsp;and npm installed on your computer</p>
</li>
<li><p>An API key from any AI assistant of your choice</p>
</li>
<li><p>A Google Cloud account and a Firebase account if you intend to deploy the applications</p>
</li>
</ul>
<h2 id="heading-the-web-speech-api">The Web Speech API</h2>
<p>The Web Speech API enables applications to transcribe the speech in audio input and also synthesise audio from text. The API is made up of two components:</p>
<ul>
<li><p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition">SpeechRecognition</a> component which receives audio input, recognises speech in the input and transcribes it</p>
</li>
<li><p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis">SpeechSynthesis</a> component which synthesises speech from text</p>
</li>
</ul>
<p>You'll use the <code>SpeechRecognition</code> component in this guide.</p>
<h3 id="heading-how-to-use-the-web-speech-api-in-javascript-for-seo">How to Use the Web Speech API in JavaScript for SEO</h3>
<p>The <code>SpeechRecognition</code> component works through a JavaScript object instantiated in code.</p>
<pre><code class="language-javascript">const recognition = new SpeechRecognition();
</code></pre>
<p>The <code>recognition</code> instance exposes several event listeners that respond to audio input. For example, the <code>audiostart</code> event fires when sound is first detected, logging <code>"audio detected"</code> to the console as shown in the snippet below.</p>
<pre><code class="language-javascript">recognition.addEventListener("audiostart", function(event){
  console.log("audio detected")
}
</code></pre>
<p>The first time it recognises speech in a sound bite, the <code>speechstart</code> event is fired.</p>
<p>A <code>SpeechRecognition</code> instance also has the ability to configure how speech recognition should work. For example, it has a property called <code>lang</code> which sets the language that it should recognise. The default value of the <code>lang</code> property is the HTML <code>lang</code> attribute value, or the browser's language setting. It also has a boolean property called <code>interimResults</code>, which when set to true, enables the instance to return transcriptions incrementally rather than waiting for the audio input to end.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/d3e7b39a-fcf2-4624-81ca-4667346c8269.png" alt="How speech is converted to transcripts via the Web Speech API" style="display:block;margin:0 auto" width="1600" height="124" loading="lazy">

<p>Audio captured by the microphone is processed by a recognition engine which could be in a remote server (for Google Chrome) or embedded in the browser (for Firefox).</p>
<p>After processing, the recognition engine returns a result, which is a list of words or phrases that have been recognised in the speech.</p>
<p>Each transcription in the list has two properties: <code>confidence</code>, a numerical estimate of its accuracy ranging from 0 (low) to 1 (high), and <code>transcript</code>, the recognised text for all or part of the speech.</p>
<h2 id="heading-how-the-application-works">How the Application Works</h2>
<p>In order for a <code>SpeechRecognition</code> instance to capture audio, it needs access to the microphone. The browser requests permission to use the microphone and, if granted, the application uses it to capture audio for the instance.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/e2847268-dfca-4f81-b929-7cc8ebd57eee.png" alt="Architecture diagram showing how Web Speech API sends transcription to a Node.js backend" style="display:block;margin:0 auto" width="1279" height="514" loading="lazy">

<p>Speech captured by the instance goes through the recognition engine and produces results or transcriptions. Results with high confidence are combined and sent to the backend via an API request.</p>
<p>The backend uses the transcript it receives to prompt an AI assistant. The response from the AI assistant is sent back to the frontend and displayed on the UI as shown in the screenshot below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/0cd3eb46-595b-4193-82a0-874e8b9f9652.png" alt="Sample image of the voice-powered application built in this guide" style="display:block;margin:0 auto" width="914" height="476" loading="lazy">

<h2 id="heading-how-to-build-the-application">How to Build the Application</h2>
<p>First, you'll build a Node.js backend application that:</p>
<ul>
<li><p>Receives a text prompt from the frontend</p>
</li>
<li><p>Sends the prompt to an AI assistant and receives a response</p>
</li>
<li><p>Returns the response of the AI assistant to the frontend</p>
</li>
</ul>
<p>Next, you'll build the frontend to:</p>
<ul>
<li><p>Accept your speech prompt, transcribe it, and display the transcription</p>
</li>
<li><p>Send the transcription result to the backend</p>
</li>
<li><p>Receive, format and display the response from the backend</p>
</li>
</ul>
<p>Optionally, you'll deploy the frontend to Firebase and the backend to Google Cloud Run, connecting them so the application is publicly accessible.</p>
<h3 id="heading-create-the-backend-application-with-nodejs">Create the Backend Application with Node.js</h3>
<p>The backend application you'll build in this section will receive text prompt from clients and use it to prompt an AI assistant. After receiving a response from the AI assistant, it will send the response back to the client.</p>
<p>We'll use Gemini in this guide, but you can use any AI assistant of your choice.</p>
<ol>
<li><p>Create a folder for the backend app and give it a name, for example, "server".</p>
</li>
<li><p>In terminal, navigate to the project folder, run the <code>npm init</code> command, and answer the follow-up questions to generate a <code>package.json</code> file</p>
</li>
<li><p>In the root of the project, create a file named <code>index.js</code>.</p>
</li>
</ol>
<p>Your project folder should have a structure like this:</p>
<pre><code class="language-plaintext">├── index.js
├── package.json
</code></pre>
<p>The <code>package.json</code> file should have the following values for <code>main</code> , <code>type</code> and <code>scripts.start</code>:</p>
<pre><code class="language-json"> { 
    "main": "index.js", 
    "type": "module", 
    "scripts": { 
       "start": "node index.js" 
    }, 
}  
</code></pre>
<ol>
<li>Copy and paste the code below into the <code>index.js</code> file to set up the server:</li>
</ol>
<pre><code class="language-javascript">import http from "node:http";

async function parseRequestBody(req) { 
    return new Promise((resolve, reject) =&gt; { 
        let data = ""; 
        req.on("data", (chunk) =&gt; (data += chunk)); 
        req.on("end", () =&gt; resolve(JSON.parse(data))); 
        req.on("error", reject); 
    }); 
}

const server = http.createServer(async function (req, res) { 
    switch (req.method) { 
        case "POST":
          return res.end("POST request received");
        default:
          return res.end("non-POST request received");
    }
})

const port = Number(process.env.PORT) || 8000; 
server.listen(port, function () { 
    console.log("server running on port", port); 
});
</code></pre>
<p>In the code snippet above, the <code>http</code> module is imported from Node.js. The <code>parseRequestBody</code> function converts the request body stream of a HTTP request to a JavaScript object.</p>
<p>It responds with <code>POST request received</code> for POST requests and <code>non-POST request received</code> for all others. By default, it listens on port 8000 unless a <code>PORT</code> environment variable is defined.</p>
<p>Run <code>npm run start</code> to start the server. To confirm it is running, execute the following command in the terminal:</p>
<pre><code class="language-shell"># For Linux/Mac, use:
curl -X POST -H "Content-Type: application/json" -d '{"prompt":"hello"}' http://localhost:8000

# For Windows, use:
curl.exe -X POST -H "Content-Type: application/json" -d '{"prompt":"hello"}' http://localhost:8000
</code></pre>
<p>You'll get the <code>POST request received</code> response from the server.</p>
<h3 id="heading-integrate-an-ai-assistant-into-the-nodejs-application">Integrate an AI Assistant into the Node.js Application</h3>
<p>In this section, you'll integrate the AI assistant into the backend application, prompt it with data sent from the frontend, and return its response to the client. Again, we'll use Gemini for this here.</p>
<p>Visit the npm page for your chosen AI assistant to learn how to install and set it up. Here are the npm pages for the most popular AI assistants:</p>
<ul>
<li><p><a href="https://www.npmjs.com/package/@anthropic-ai/sdk">Anthropic AI</a></p>
</li>
<li><p><a href="https://www.npmjs.com/package/@google/genai">Google Gemini</a></p>
</li>
<li><p><a href="https://www.npmjs.com/package/openai">Open AI</a></p>
</li>
</ul>
<p>Update the <code>index.js</code> file to include the setup for the AI assistant using the snippet below:</p>
<pre><code class="language-javascript">import http from "node:http";
import { GoogleGenAI } from "@google/genai"; 

const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });

async function parseRequestBody(req) { /* minimised code */ }

const server = http.createServer(async function (req, res) {
    res.setHeader("Access-Control-Allow-Origin", "*");

    switch (req.method) { 
        case "POST":
          const body = await parseRequestBody(req);
          const response = await ai.models.generateContent({
            model: "gemini-2.5-flash", // or whatever model you have
            contents: body.prompt,
         });

         return res.end(response.text);

        default:
          return res.end("non-POST request received");
    }
}
/* previous code minimised*/
</code></pre>
<p>The <code>GEMINI_API_KEY</code> is retrieved from the environment variables and passed as the <code>apiKey</code> to <code>GoogleGenAI</code>, which initialises the AI assistant.</p>
<p>The POST request body is parsed into a JavaScript object, and <code>body.prompt</code> is passed to <code>ai.models.generateContent</code> to prompt the AI assistant. The <code>text</code> property of the response which is in Markdown format, is then returned to the client.</p>
<p>Restart the server and test the current setup by making an API request to it with curl using the snippet below:</p>
<pre><code class="language-shell"># For Linux/Mac:

curl -X POST -H "Content-Type: application/json" -d '{"prompt":"hello"}' http://localhost:8000

# For Windows:

curl.exe -X POST -H "Content-Type: application/json" -d '{"prompt":"hello"}' http://localhost:8000
</code></pre>
<p>You'll get an AI text response in the form of Markdown.</p>
<h3 id="heading-create-the-frontend-application-with-vite">Create the Frontend Application with Vite</h3>
<p><a href="https://vite.dev/">Vite</a> is a build tool that provides a faster and more seamless development experience for developing applications. You'll use Vite to create the frontend application and connect it with the backend application from the previous section.</p>
<p>In another folder, create a project with Vite by running the <code>npm create vite@latest</code> command and answer the prompts:</p>
<pre><code class="language-shell">npm create vite@latest

Need to install the following packages:
create-vite@8.1.0
Ok to proceed? (y) y

&gt; npx create-vite

◇  Project name:
│  [name-of-your-frontend-app] e.g prompt-ai-with-speech-frontend
│
◇  Select a framework:
│  Vanilla
│
◇  Select a variant:
│  JavaScript
│
◇  Use rolldown-vite (Experimental)?:
│  No
│
◇  Install with npm and start now?
│  Yes
</code></pre>
<p>Open the project created in your code editor and make the following updates:</p>
<p>First, replace the content of <code>index.html</code> with the code snippet below:</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
    &lt;title&gt;Prompt AI with the Web Speech Recognition API&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;main id="app"&gt;
      &lt;section&gt;
        &lt;h1&gt;Prompt AI with the Web Speech Recognition API&lt;/h1&gt;
        &lt;ul id="ulist_chat"&gt;&lt;/ul&gt;
      &lt;/section&gt;
      &lt;div class="btn_container"&gt;
        &lt;button id="btn_record"&gt;Record prompt&lt;/button&gt;
      &lt;/div&gt;
    &lt;/main&gt;
    &lt;script type="module" src="/src/main.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>Then replace the content of <code>src/style.css</code> with the code snippet below:</p>
<pre><code class="language-css">:root {
  font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;

  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.87);
  background-color: #242424;

  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

button {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
  background-color: #1a1a1a;
  cursor: pointer;
  transition: border-color 0.25s;
}
button:hover {
  border-color: #646cff;
}
button:focus,
button:focus-visible {
  outline: 4px auto -webkit-focus-ring-color;
}
.btn_container {
  padding: 16px 0px;
  display: flex;
  justify-content: center;
}

#ulist_chat {
  display: flex;
  flex-direction: column;
  width: 80%;
  margin: auto;
  padding: 0;
}

#ulist_chat .transcript {
  border: 1px solid tomato;
  background: #fce5e5af;
  border-radius: 4px;
  align-self: flex-end;
  list-style-type: none;
  margin: 8px;
  padding: 8px;
  max-width: 80%;
}

#ulist_chat .ai_response p { 
  margin: 2px; 
}

#ulist_chat .ai_response {
  border: 1px solid green;
  background: #e5fce8af;
  border-radius: 4px;
  align-self: flex-start;
  list-style-type: none;
  margin: 8px;
  padding: 8px;
  max-width: 80%;
}

@media (prefers-color-scheme: light) {
  :root {
    color: #000;
    background-color: #ffffff;
  }
  a:hover {
    color: #747bff;
  }
  button {
    background-color: #f9f9f9;
  }
}
</code></pre>
<p>Now replace the content of <code>src/main.js</code> with the code snippet below:</p>
<pre><code class="language-javascript">import "./style.css";
import { marked } from "marked";

const apiUrl = "http://localhost:8000";
const btnRecord = document.getElementById("btn_record");
const uListChat = document.getElementById("ulist_chat");

function ensureBrowserHasSpeechAPI() {
  if (
    !("webkitSpeechRecognition" in window) &amp;&amp;
    !("SpeechRecognition" in window)
  ) {
    btnRecord.style.display = "none";

    return alert(
      "This browser does not have the features required for this demo. Use Google Chrome &gt;= v33"
    );
  }

  start();
}

function toggleRecording(config, listener) {
  if (config.isListening) {
    config.isListening = false;
    btnRecord.innerText = "Start recording";
    return listener.stop();
  }

  config.isListening = true;
  btnRecord.innerText = "Stop recording";

  return listener.start();
}

/** @param {string} transcript  */
function appendTranscriptToChatList(transcript) {
  const li = document.createElement("li");
  li.innerText = transcript;
  li.classList.add("transcript");
  uListChat.appendChild(li);
}

/** @param {string} aiResponse  */
function appendAIResponseToChatList(aiResponse) {
  const li = document.createElement("li");
  li.innerHTML = marked.parse(aiResponse);
  li.classList.add("ai_response");
  uListChat.appendChild(li);
}

/** @param {string} prompt  */
async function promptAI(prompt) {
  try {
    const response = await fetch(apiUrl, {
      body: JSON.stringify({ prompt }),
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
    });

    if (!response.ok) {
      const err = await response.text();
      console.error(err);
      alert("An error occurred. Try again");
      return;
    }

    const text = await response.text();
    return text;
  } catch (error) {
    logError(error);
    alert("An error occurred. Try again");
    return ""
  }
}

function setUpSpeechRecognition() {
  const SpeechRecognition =
    window.SpeechRecognition || window.webkitSpeechRecognition;

  const listener = new SpeechRecognition();
  listener.continuous = true; // listen for long speech
  listener.maxAlternatives = 2; // only two transcription suggestions required
  let transcript = "";

  // automatic: onstart -&gt; onaudiostart -&gt; onsoundstart -&gt; onspeechstart
  // automatic: onspeechend -&gt; onsoundend -&gt; onaudioend -&gt; onresult -&gt; onend
  // click button: onaudioend -&gt; onresult -&gt; onend

  listener.onend = async function () {
    if (!transcript || !transcript.trim()) return;

    btnRecord.innerText = "Thinking...";
    btnRecord.disabled = true;
    appendTranscriptToChatList(transcript);
    promptAI(transcript)
      .then(function (res) {
        appendAIResponseToChatList(res);
      })
      .finally(function () {
        btnRecord.innerText = "Record prompt";
        btnRecord.disabled = false;
        transcript = "";
      });
  };

  listener.onerror = function (err) {
    logError(err);
    alert("Error occurred while capturing speech");
  };

  listener.onresult = function (event) {
    for (const alternatives of event.results) {
      const [bestAlternative] = Array.from(alternatives).toSorted(
        (altA, altB) =&gt; altB.confidence - altA.confidence
      );

      transcript += bestAlternative.transcript;
    }
  };

  return listener;
}

async function start() {
  const config = {
    isListening: false,
  };

  const listener = setUpSpeechRecognition();

  btnRecord.addEventListener("click", function () {
    toggleRecording(config, listener);
  });
}

ensureBrowserHasSpeechAPI();

function logError(...str) {
  for (const s of str) {
    console.error("error:", s);
  }
}
</code></pre>
<p><a href="https://www.npmjs.com/package/marked"><code>marked</code></a> is an npm package that helps convert Markdown text to HTML and it's a required dependency in the project. Install <code>marked</code> in the project by running the following command in the project's terminal:</p>
<pre><code class="language-shell">npm install marked
</code></pre>
<p>The <code>ensureBrowserHasSpeechAPI</code> function in <code>src/main.js</code> checks to see if the browser in use has the <code>WebSpeechAPI</code> feature. If it doesn't, it prevents the application from displaying the controls for the UI. That's why you'll need a Google Chrome browser with a version greater than or equal to 33 for this guide. Those versions have the <code>WebSpeechAPI</code> feature.</p>
<p>The <code>toggleRecording</code> function executes when the <strong>Record prompt</strong> button is clicked. On the first click, it requests microphone permission. It also enables/disables the activity of the <code>SpeechRecognition</code> instance.</p>
<p>The <code>setUpSpeechRecognition</code> function sets up the <code>SpeechRecognition</code> instance: <code>listener</code>, and its configuration. It also attaches functions to be run when the <code>end</code>, <code>error</code> and <code>result</code> events are triggered.</p>
<ul>
<li><p><code>error</code> is triggered when there is an error in capturing or processing audio</p>
</li>
<li><p><code>result</code> is triggered when the recognition engine returns transcription results</p>
</li>
<li><p><code>end</code> is triggered when the speech recognition service has disconnected from the application.</p>
</li>
</ul>
<p>The transcript is displayed on the UI after passing it as an argument to the <code>appendTranscriptToChatList</code> function.</p>
<p>The <code>promptAI</code> function executes when the <code>end</code> event fires, accepting the speech transcript as an argument and sending it to the backend via a POST request using <code>fetch</code>. On success, the AI response is returned as Markdown and passed to <code>appendAIResponseToChatList</code>, which converts it to HTML and displays it on the UI.</p>
<h2 id="heading-test-the-application-locally">Test the Application Locally</h2>
<p>Start the backend application by running <code>npm run start</code> in the backend project's terminal and start the frontend application by running <code>npm run dev</code> in the frontend project's terminal. Visit <code>http://localhost:5173</code> to view the UI of application. You should see a UI similar to the one in the image below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/c5d6fc8b-d693-4b83-9611-8e0c493c9f7c.png" alt="Initial image of the voice-powered AI chat app UI built with the Web Speech API" style="display:block;margin:0 auto" width="1068" height="273" loading="lazy">

<p>Click the <strong>Record prompt</strong> button. A prompt will appear requesting microphone permission. Select "Allow while visiting the site" or "Allow this time" to grant access and begin recording. Click on the <strong>Stop recording</strong> button when you're done.</p>
<p>The UI will display the transcript of your speech and the application will send it to the backend as a prompt. After waiting for a short while, you'll see the response from the AI assistant displayed on the UI.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66e28b713f978a0e2cd2b763/7a4ff532-6bd0-478c-ae3d-f6446c9d0a1f.png" alt="Final image of the voice-powered AI chat app UI built with the Web Speech API" style="display:block;margin:0 auto" width="914" height="476" loading="lazy">

<p>You have been able to use speech input to prompt an AI assistant, receive a response and display it. How do you make this application accessible to everyone? The next section guides you through deploying both applications.</p>
<h2 id="heading-deploy-the-backend-application-with-google-cloud-run">Deploy the Backend Application with Google Cloud Run</h2>
<p>In this section, you'll deploy the backend application with Google Cloud Run and get a URL which will be used as the <code>apiUrl</code> in the frontend application.</p>
<p>In order to host the backend application with Google Cloud Run, you need to have a:</p>
<ul>
<li><p>Google Cloud developer account</p>
</li>
<li><p>Google Cloud project</p>
</li>
</ul>
<p>Visit <a href="https://cloud.google.com/">Google Cloud</a> to create an account and create a project. You can name the project whatever you want but it's a good idea to give a descriptive name. Take note of the project's ID because you'll use it in the deployment process.</p>
<p>There are three ways to deploy applications on Google Cloud Run:</p>
<ul>
<li><p>Deploy a revision from an existing container image</p>
</li>
<li><p>Deploy from a repository such as GitHub or GitLab</p>
</li>
<li><p>Create a function using the inline editor</p>
</li>
</ul>
<p>You can see all three options if you visit the <a href="https://console.cloud.google.com/run/create">create Cloud Run service</a> page.</p>
<p>In this guide, you'll use the option to deploy from an existing container image. Follow the steps below to deploy the backend server from a container image or follow the Cloud Run documentation at <a href="https://docs.cloud.google.com/run/docs/quickstarts/build-and-deploy/deploy-nodejs-service">build and deploy Node.js service on Cloud Run</a>:</p>
<ul>
<li><p>Install the Google Cloud (gcloud) CLI on your computer by visiting the <a href="https://docs.cloud.google.com/sdk/docs/install">Install Google Cloud CLI</a> page and following the instructions on the page for your operating system</p>
</li>
<li><p>Initialise the gcloud CLI to connect it to your developer account by visiting the <a href="https://docs.cloud.google.com/sdk/docs/initializing">Initializing the gcloud CLI</a> page and following the instructions on the page</p>
</li>
<li><p>Set the project you want to deploy the backend server under by running the command below in your terminal:</p>
</li>
</ul>
<pre><code class="language-shell"># replace PROJECT_ID with your project ID

gcloud config set project PROJECT_ID
</code></pre>
<ul>
<li><p>Visit your project's <a href="https://console.cloud.google.com/iam-admin/iam">IAM Admin</a> page to enable the following roles on the service account created for this project:</p>
<ul>
<li><p><code>roles/run.sourceDeveloper</code></p>
</li>
<li><p><code>roles/iam.serviceAccountUser</code></p>
</li>
<li><p><code>roles/logging.viewer</code></p>
</li>
</ul>
</li>
</ul>
<p>These roles are required to enable the Cloud Run Admin API and Cloud Build APIs. Take note of the service account email address.</p>
<ul>
<li>Enable the Cloud Run Admin API and Cloud Build APIs by running the code snippet below in your terminal:</li>
</ul>
<pre><code class="language-shell">gcloud services enable run.googleapis.com cloudbuild.googleapis.com
</code></pre>
<ul>
<li>Grant the Cloud Build service account access to your project by running the code snippet below in your terminal:</li>
</ul>
<pre><code class="language-plaintext"># replace PROJECT_ID with your project ID and 
# SERVICE_ACCOUNT_EMAIL_ADDRESS with the service account's email address

gcloud projects add-iam-policy-binding PROJECT_ID \
--member=serviceAccount:SERVICE_ACCOUNT_EMAIL_ADDRESS \
--role=roles/run.builder
</code></pre>
<p>Update <code>index.js</code> in the backend project to restrict API requests to clients specified in the <code>ALLOWED_ORIGINS</code> environment variable, and update the AI assistant configuration to use the API key loaded from environment variables.</p>
<pre><code class="language-javascript">// Use the API key from the environment variable
const GEMINI_API_KEY = process.env.GEMINI_API_KEY; 
const ai = new GoogleGenAI({ apiKey: GEMINI_API_KEY });

// Replace res.setHeader("Access-Control-Allow-Origin", "*"); with
res.setHeader("Access-Control-Allow-Origin", process.env.ALLOWED_ORIGINS);
res.setHeader("Access-Control-Allow-Methods", "POST,OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
</code></pre>
<p>This ensures that the application will receive POST requests from only frontend URLs specified in the <code>ALLOWED_ORIGINS</code> environment variable. This setup prevents the backend from being loaded with requests from frontend clients that you don't know, and also prevents the excess use of your tokens. It also keeps you from deploying the application with the AI API key hardcoded in it.</p>
<p>To test that the new changes work, run the backend application with the command below:</p>
<pre><code class="language-shell"># replace YOUR_API_KEY with your Gemini API key

GEMINI_API_KEY=YOUR_API_KEY ALLOWED_ORIGINS="http://localhost:5173" npm run start
</code></pre>
<p>With the command in the code snippet above, the backend application will not respond to requests from frontend applications not hosted on <code>http://localhost:5173</code>. Try to send a prompt from the frontend application to test that it works.</p>
<p>To deploy the backend application to Cloud Run, run the command in the snippet below in the terminal of the backend project folder. The command sets the environment variables required for the application to run and also deploys it to Google Cloud Run.</p>
<pre><code class="language-plaintext"># replace &lt;api-key&gt; with your Gemini API key

gcloud run deploy --source . \
--set-env-vars "ALLOWED_ORIGINS=http://localhost:5173" \
--set-env-vars "GEMINI_API_KEY=&lt;api-key&gt;"
</code></pre>
<p>Once deployment is complete, you'll receive the URL of your hosted backend. Copy it and replace the value of <code>apiUrl</code> in your frontend application with it. Run the frontend, record a prompt, and confirm that everything works as expected.</p>
<h2 id="heading-deploy-the-frontend-application-with-firebase">Deploy the Frontend Application with Firebase</h2>
<p>In this section, you'll host the frontend application with Firebase. You need to have a Firebase account. Follow the steps below to host the frontend with Firebase:</p>
<ul>
<li><p>Create and set up a Firebase project</p>
</li>
<li><p>Install the Firebase CLI by visiting the <a href="https://firebase.google.com/docs/cli#install_the_firebase_cli">install Firebase CLI</a> page and follow the instructions for your operating system</p>
</li>
<li><p>In the terminal of the frontend project, run <code>firebase init hosting</code> to initialise the hosting configuration for the project. Follow the prompts and use <code>dist</code> as the public directory when prompted</p>
</li>
<li><p>Run <code>firebase deploy --only hosting</code> to host the application with Firebase</p>
</li>
</ul>
<p>Once deployment is complete, you will receive the URL of your hosted frontend application.</p>
<h2 id="heading-connect-the-deployed-applications">Connect the Deployed Applications</h2>
<p>Remember that the first time you deployed your backend application, you set <code>ALLOWED_ORIGINS</code> to <code>http://localhost:5173</code>. The deployed backend application doesn't know about the URL of the deployed frontend application so it won't accept requests from it.</p>
<p>In the terminal of the backend application, deploy the backend application again using the command in the snippet below:</p>
<pre><code class="language-shell"># replace &lt;frontend-url&gt; with your Firebase frontend URL and &lt;api-key&gt; 
# with your Gemini API key

gcloud run deploy --source . \
--set-env-vars "ALLOWED_ORIGINS=&lt;frontend-url&gt;" --set-env-vars "GEMINI_API_KEY=&lt;api-key&gt;"
</code></pre>
<p>Visit the deployed frontend application and test it. It should work without errors.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this guide, you built a frontend application that captures and transcribes speech, a Node.js backend application that prompts AI, and you connected both applications together to build a simplified version of the <strong>Use Voice</strong> feature in AI chat applications.</p>
<p>Can you add a feature to the application that will make it read out the response from the backend when it receives it? You can use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis"><code>SpeechSynthesis</code> API</a> to build it.</p>
<p>Feel free to <a href="https://www.linkedin.com/in/orimdominicadah/">connect with me on LinkedIn</a> if you have any questions. Thank you for reading this far and don’t hesitate to share this article if you found it insightful. Cheers!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Ship a Production-Ready RAG App with FAISS (Guardrails, Evals, and Fallbacks) ]]>
                </title>
                <description>
                    <![CDATA[ Most LLM applications look great in a high-fidelity demo. Then they hit the hands of real users and start failing in very predictable yet damaging ways. They answer questions they should not, they bre ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-rag-app-faiss-fastapi/</link>
                <guid isPermaLink="false">69b841572ad6ae5184d54317</guid>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ FastAPI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ llm ]]>
                    </category>
                
                    <category>
                        <![CDATA[ RAG  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vector database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ faiss ]]>
                    </category>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chidozie Managwu ]]>
                </dc:creator>
                <pubDate>Mon, 16 Mar 2026 17:43:51 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/f9da3ad9-e285-4ce1-acb7-ad119579971c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most LLM applications look great in a high-fidelity demo. Then they hit the hands of real users and start failing in very predictable yet damaging ways.</p>
<p>They answer questions they should not, they break when document retrieval is weak, they time out due to network latency, and nobody can tell exactly what happened because there are no logs and no tests.</p>
<p>In this tutorial, you’ll build a beginner-friendly Retrieval Augmented Generation (RAG) application designed to survive production realities. This isn’t just a script that calls an API. It’s a system featuring a FastAPI backend, a persisted FAISS vector store, and essential safety guardrails (including a retrieval gate and fallbacks).</p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<ol>
<li><p><a href="#heading-why-rag-alone-does-not-equal-productionready">Why RAG Alone Does Not Equal Production-Ready</a></p>
</li>
<li><p><a href="#heading-the-architecture-you-are-building">The Architecture You Are Building</a></p>
</li>
<li><p><a href="#heading-project-setup-and-structure">Project Setup and Structure</a></p>
</li>
<li><p><a href="#heading-how-to-build-the-rag-layer-with-faiss">How to Build the RAG Layer with FAISS</a></p>
</li>
<li><p><a href="#heading-how-to-add-the-llm-call-with-structured-output">How to Add the LLM Call with Structured Output</a></p>
</li>
<li><p><a href="#heading-how-to-add-guardrails-retrieval-gate-and-fallbacks">How to Add Guardrails: Retrieval Gate and Fallbacks</a></p>
</li>
<li><p><a href="#heading-fast-api-app-creating-the-answer-endpoint">FastAPI App: Creating the /answer Endpoint</a></p>
</li>
<li><p><a href="#heading-how-to-add-beginnerfriendly-evals">How to Add Beginner-Friendly Evals</a></p>
</li>
<li><p><a href="#heading-what-to-improve-next-realistic-upgrades">What to Improve Next: Realistic Upgrades</a></p>
</li>
</ol>
<h2 id="heading-why-rag-alone-does-not-equal-production-ready">Why RAG Alone Does Not Equal Production-Ready</h2>
<p>Retrieval Augmented Generation (RAG) is often hailed as the hallucination killer. By grounding the model in retrieved text, we provide it with the facts it needs to be accurate. But simply connecting a vector database to an LLM isn’t enough for a production environment.</p>
<p>Production issues usually arise from the silent failures in the system surrounding the model:</p>
<ul>
<li><p><strong>Weak retrieval:</strong> If the app retrieves irrelevant chunks of text, the model tries to bridge the gap by inventing an answer anyway. Without a designated “I do not know” path, the model is essentially forced to hallucinate.</p>
</li>
<li><p><strong>Lack of visibility:</strong> Without structured outputs and basic logging, you can’t tell if bad retrieval, a confusing prompt, or a model update caused a wrong answer.</p>
</li>
<li><p><strong>Fragility:</strong> A simple API timeout or malformed provider response becomes a user-facing outage if you don’t implement fallbacks.</p>
</li>
<li><p><strong>No regression testing:</strong> In traditional software, we have unit tests. In AI, we need evals. Without them, a small tweak to your prompt might fix one issue but break ten others without you realising it.</p>
</li>
</ul>
<p>We’ll solve each of these issues systematically in this guide.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>This tutorial is beginner-friendly, but it assumes you have a few basics in place so you can focus on building a robust RAG system instead of getting stuck on setup issues.</p>
<h3 id="heading-knowledge">Knowledge</h3>
<p>You should be comfortable with:</p>
<ul>
<li><p><strong>Python fundamentals</strong> (functions, modules, virtual environments)</p>
</li>
<li><p><strong>Basic HTTP + JSON</strong> (requests, response payloads)</p>
</li>
<li><p><strong>APIs with FastAPI</strong> (what an endpoint is and how to run a server)</p>
</li>
<li><p><strong>High-level LLM concepts</strong> (prompting, temperature, structured outputs)</p>
</li>
</ul>
<h3 id="heading-tools-accounts">Tools + Accounts</h3>
<p>You’ll need:</p>
<ul>
<li><p><strong>Python 3.10+</strong></p>
</li>
<li><p>A working <strong>OpenAI-compatible API key</strong> (OpenAI or any provider that supports the same request/response shape)</p>
</li>
<li><p>A local environment where you can run a FastAPI app (Mac/Linux/Windows)</p>
</li>
</ul>
<h3 id="heading-what-this-tutorial-covers-and-what-it-doesnt">What This Tutorial Covers (and What It Doesn’t)</h3>
<p>We’ll build a production-minded baseline:</p>
<ul>
<li><p>A <strong>FAISS-backed retriever</strong> with a persisted index + metadata</p>
</li>
<li><p>A <strong>retrieval gate</strong> to prevent “forced hallucination”</p>
</li>
<li><p><strong>Structured JSON outputs</strong> so your backend is stable</p>
</li>
<li><p><strong>Fallback behavior</strong> for timeouts and provider errors</p>
</li>
<li><p>A small <strong>eval harness</strong> to prevent regressions</p>
</li>
</ul>
<p>We won’t implement advanced upgrades such as rerankers, semantic chunking, auth, background jobs beyond a roadmap at the end.</p>
<h2 id="heading-the-architecture-you-are-building">The Architecture You Are Building</h2>
<p>The flow of our application follows a disciplined path so every answer is grounded in evidence:</p>
<ol>
<li><p><strong>User query:</strong> The user submits a question via a FastAPI endpoint.</p>
</li>
<li><p><strong>Retrieval:</strong> The system embeds the question and retrieves the top-k most similar document chunks.</p>
</li>
<li><p><strong>The retrieval gate:</strong> We evaluate the similarity score. If the context is not relevant enough, we stop immediately and refuse the query.</p>
</li>
<li><p><strong>Augmentation and generation:</strong> If the gate passes, we send a context-augmented prompt to the LLM.</p>
</li>
<li><p><strong>Structured response:</strong> The model returns a JSON object containing the answer, sources used, and a confidence level.</p>
</li>
</ol>
<h2 id="heading-project-setup-and-structure">Project Setup and Structure</h2>
<p>To keep things organized and maintainable, we’ll use a modular structure. This allows you to swap out your LLM provider or your vector database without rewriting your entire core application.</p>
<h3 id="heading-project-structure">Project Structure</h3>
<pre><code class="language-python">.
├── app.py              # FastAPI entry point and API logic
├── rag.py              # FAISS index, persistence, and document retrieval
├── llm.py              # LLM API interface and JSON parsing
├── prompts.py          # Centralized prompt templates
├── data/               # Source .txt documents
├── index/              # Persisted FAISS index and metadata
└── evals/              # Evaluation dataset and runner script
    ├── eval_set.json
    └── run_evals.py
</code></pre>
<h3 id="heading-install-dependencies">Install Dependencies</h3>
<p>First, create a virtual environment to isolate your project:</p>
<pre><code class="language-python">python -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate
pip install fastapi uvicorn faiss-cpu numpy pydantic requests python-dotenv
</code></pre>
<h3 id="heading-configure-the-environment">Configure the Environment</h3>
<p>Create a <code>.env</code> file in the root directory. We are targeting OpenAI-compatible providers:</p>
<pre><code class="language-python">OPENAI_API_KEY=your_actual_api_key_here
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o-mini
</code></pre>
<p>Important note on compatibility: The code below assumes an OpenAI-style API. If you use a provider that is not compatible, you must change the URL, headers (for example <code>X-API-Key</code>), and the way you extract embeddings and final message content in <code>embed_texts()</code> and <code>call_llm()</code>.</p>
<h2 id="heading-how-to-build-the-rag-layer-with-faiss">How to Build the RAG Layer with FAISS</h2>
<p>In <code>rag.py</code>, we handle the “Retriever” part of RAG. This involves turning raw text into mathematical vectors that the computer can compare.</p>
<h3 id="heading-what-is-faiss-and-what-does-it-do">What is FAISS (and What Does It Do)?</h3>
<p><strong>FAISS</strong> (Facebook AI Similarity Search) is a fast library for vector similarity search. In a RAG system, each chunk of text becomes an embedding vector (a list of floats). FAISS stores those vectors in an index so you can quickly ask:</p>
<blockquote>
<p>“Given this question embedding, which document chunks are closest to it?”</p>
</blockquote>
<p>In this tutorial, we use <code>IndexFlatIP</code> inner product and normalise vectors with <code>faiss.normalize_L2(...)</code>. With normalised vectors, the inner product behaves like <strong>cosine similarity</strong>, giving us a stable score we can use for a retrieval gate.</p>
<h3 id="heading-chunking-strategy-with-overlap">Chunking Strategy With Overlap</h3>
<p>We’ll use chunking with overlap. If we split a document at exactly 1,000 characters, we might cut a sentence in half, losing its meaning. By using an overlap, for example, 200 characters, we ensure that the end of one chunk and the beginning of the next share context.</p>
<h3 id="heading-implementation-of-ragpy">Implementation of <code>rag.py</code></h3>
<pre><code class="language-python">import os
import faiss
import numpy as np
import requests
import json
from typing import List, Dict
from dotenv import load_dotenv

load_dotenv()

INDEX_PATH = "index/faiss.index"
META_PATH = "index/meta.json"

def chunk_text(text: str, size: int = 1000, overlap: int = 200) -&gt; List[str]:
    chunks = []
    step = max(1, size - overlap)
    for i in range(0, len(text), step):
        chunk = text[i : i + size].strip()
        if chunk:
            chunks.append(chunk)
    return chunks

def embed_texts(texts: List[str]) -&gt; np.ndarray:
    # Note: If your provider is not OpenAI-compatible, change this URL and headers
    url = f"{os.getenv('OPENAI_BASE_URL')}/embeddings"
    headers = {"Authorization": f"Bearer {os.getenv('OPENAI_API_KEY')}"}
    payload = {"input": texts, "model": "text-embedding-3-small"}

    resp = requests.post(url, headers=headers, json=payload, timeout=30)
    resp.raise_for_status()
    # If your provider uses a different response format, change the line below
    vectors = np.array([item["embedding"] for item in resp.json()["data"]], dtype="float32")
    return vectors

def build_index() -&gt; None:
    all_chunks: List[str] = []
    metadata: List[Dict] = []

    if not os.path.exists("data"):
        os.makedirs("data")
        return

    for file in os.listdir("data"):
        if not file.endswith(".txt"):
            continue

        with open(f"data/{file}", "r", encoding="utf-8") as f:
            text = f.read()

        chunks = chunk_text(text)
        all_chunks.extend(chunks)
        for c in chunks:
            metadata.append({"source": file, "text": c})

    if not all_chunks:
        return

    embeddings = embed_texts(all_chunks)
    faiss.normalize_L2(embeddings)

    dim = embeddings.shape[1]
    index = faiss.IndexFlatIP(dim)
    index.add(embeddings)

    os.makedirs("index", exist_ok=True)
    faiss.write_index(index, INDEX_PATH)

    with open(META_PATH, "w", encoding="utf-8") as f:
        json.dump(metadata, f, ensure_ascii=False)

def load_index():
    if not (os.path.exists(INDEX_PATH) and os.path.exists(META_PATH)):
        raise FileNotFoundError(
            "FAISS index not found. Add .txt files to data/ and run build_index()."
        )

    index = faiss.read_index(INDEX_PATH)
    with open(META_PATH, "r", encoding="utf-8") as f:
        metadata = json.load(f)
    return index, metadata

def retrieve(query: str, k: int = 5) -&gt; List[Dict]:
    index, metadata = load_index()

    q_emb = embed_texts([query])
    faiss.normalize_L2(q_emb)

    scores, ids = index.search(q_emb, k)
    results = []
    for score, idx in zip(scores[0], ids[0]):
        if idx == -1:
            continue
        m = metadata[idx]
        results.append(
            {"score": float(score), "source": m["source"], "text": m["text"], "id": int(idx)}
        )
    return results
</code></pre>
<h2 id="heading-how-to-add-the-llm-call-with-structured-output">How to Add the LLM Call with Structured Output</h2>
<p>A major failure point in AI apps is the “chatty” nature of LLMs. If your backend expects a list of sources but the LLM returns conversational filler, your code will crash.</p>
<p>We solve this with <strong>structured output</strong>: instruct the model to return a strict JSON object, then parse it safely.</p>
<h3 id="heading-implementation-of-llmpy">Implementation of <code>llm.py</code></h3>
<pre><code class="language-python">import json
import requests
import os
from typing import Dict, Any

def call_llm(system_prompt: str, user_prompt: str) -&gt; Dict[str, Any]:
    # Note: Change URL/Headers if using a non-OpenAI compatible provider
    url = f"{os.getenv('OPENAI_BASE_URL')}/chat/completions"
    headers = {
        "Authorization": f"Bearer {os.getenv('OPENAI_API_KEY')}",
        "Content-Type": "application/json",
    }

    payload = {
        "model": os.getenv("OPENAI_MODEL"),
        "messages": [
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt},
        ],
        "response_format": {"type": "json_object"},
        "temperature": 0,
    }

    try:
        resp = requests.post(url, headers=headers, json=payload, timeout=30)
        resp.raise_for_status()
        content = resp.json()["choices"][0]["message"]["content"]

        parsed = json.loads(content)
        parsed.setdefault("answer", "")
        parsed.setdefault("refusal", False)
        parsed.setdefault("confidence", "medium")
        parsed.setdefault("sources", [])
        return parsed

    except (requests.Timeout, requests.ConnectionError):
        return {
            "answer": "The system is temporarily unavailable (network issue). Please try again.",
            "refusal": True,
            "confidence": "low",
            "sources": [],
            "error_type": "network_error",
        }
    except Exception:
        return {
            "answer": "A system error occurred while generating the answer.",
            "refusal": True,
            "confidence": "low",
            "sources": [],
            "error_type": "unknown_error",
        }
</code></pre>
<h2 id="heading-how-to-add-guardrails-retrieval-gate-and-fallbacks">How to Add Guardrails: Retrieval Gate and Fallbacks</h2>
<p>Guardrails are interceptors. They sit between the user and the model to prevent predictable failures.</p>
<h3 id="heading-the-retrieval-gate-how-it-works-and-how-to-add-it">The Retrieval Gate: How It Works and How to Add It</h3>
<p>In a standard RAG pipeline, the system always calls the LLM. If the user asks an irrelevant question, the retriever will still return the “closest” (but wrong) chunks.</p>
<p>The solution is the retrieval gate:</p>
<ol>
<li><p>Retrieve top-k chunks and get the <strong>top similarity score</strong></p>
</li>
<li><p>If the score is below a threshold (for example <code>0.30</code>), refuse immediately</p>
</li>
<li><p>Only call the LLM when retrieval is strong enough to ground the answer</p>
</li>
</ol>
<p>A threshold of <code>0.30</code> is a reasonable starting point when using normalised cosine similarity, but you should tune it using evals (next section).</p>
<h3 id="heading-fallbacks-and-why-they-matter">Fallbacks and Why They Matter</h3>
<p>Fallbacks ensure that if an API fails or times out, the user gets a helpful message instead of a crash. They also keep your API response shape consistent, which prevents frontend errors and makes logging meaningful.</p>
<p>In this tutorial, fallbacks are implemented inside <code>call_llm()</code> so your FastAPI layer stays simple.</p>
<h2 id="heading-fastapi-app-creating-the-answer-endpoint">FastAPI App: Creating the /answer Endpoint</h2>
<p>The <code>app.py</code> file is the conductor. It ties retrieval, guardrails, prompting, and generation together.</p>
<h3 id="heading-implementation-of-apppy">Implementation of <code>app.py</code></h3>
<pre><code class="language-python">from fastapi import FastAPI
from pydantic import BaseModel
from rag import retrieve
from llm import call_llm
import prompts
import time
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("rag_app")

app = FastAPI(title="Production-Ready RAG")

class QueryRequest(BaseModel):
    question: str

@app.post("/answer")
async def get_answer(req: QueryRequest):
    start_time = time.time()
    question = (req.question or "").strip()

    if not question:
        return {
            "answer": "Please provide a non-empty question.",
            "refusal": True,
            "confidence": "low",
            "sources": [],
            "latency_sec": round(time.time() - start_time, 2),
        }

    # 1) Retrieval
    results = retrieve(question, k=5)
    top_score = results[0]["score"] if results else 0.0

    logger.info("query=%r top_score=%.3f num_results=%d", question, top_score, len(results))

    # 2) Retrieval Gate (Guardrail)
    if top_score &lt; 0.30:
        return {
            "answer": "I do not have documents to answer that question.",
            "refusal": True,
            "confidence": "low",
            "sources": [],
            "latency_sec": round(time.time() - start_time, 2),
            "retrieval": {"top_score": top_score, "k": 5},
        }

    # 3) Augment
    context_text = "\n\n".join([f"Source {r['source']}: {r['text']}" for r in results])
    user_prompt = f"Context:\n{context_text}\n\nQuestion: {question}"

    # 4) Generation with Fallback
    response = call_llm(prompts.SYSTEM_PROMPT, user_prompt)

    # 5) Attach debug metadata
    response["latency_sec"] = round(time.time() - start_time, 2)
    response["retrieval"] = {"top_score": top_score, "k": 5}
    return response
</code></pre>
<h2 id="heading-centralized-prompt-template-promptspy">Centralized Prompt – Template: prompts.py</h2>
<p>A small but important habit: keep prompts centralised so they’re versionable and easy to evaluate.</p>
<h3 id="heading-example-promptspy">Example <code>prompts.py</code></h3>
<pre><code class="language-python">SYSTEM_PROMPT = """You are a RAG assistant. Use ONLY the provided Context to answer.
If the context does not contain the answer, respond with refusal=true.

Return a valid JSON object with exactly these keys:
- answer: string
- refusal: boolean
- confidence: "low" | "medium" | "high"
- sources: array of strings (source filenames you used)

Do not include any extra keys. Do not include markdown. Do not include commentary."""
</code></pre>
<h2 id="heading-how-to-add-beginner-friendly-evals">How to Add Beginner-Friendly Evals</h2>
<p>In AI systems, outputs are probabilistic. This makes testing harder than traditional software. Evals (evaluations) are a set of “golden questions” and “expected behaviours” you run repeatedly to detect regressions.</p>
<p>Instead of “does it output exactly this string,” you test:</p>
<ul>
<li><p>Should the app <strong>refuse</strong> when the retrieval is weak?</p>
</li>
<li><p>When it answers, does it include <strong>sources</strong>?</p>
</li>
<li><p>Is the behaviour stable across prompt tweaks and model changes?</p>
</li>
</ul>
<h3 id="heading-step-1-create-evalsevalsetjson">Step 1: Create <code>evals/eval_set.json</code></h3>
<p>This should contain both positive and negative cases.</p>
<pre><code class="language-json">[
  {
    "id": "in_scope_01",
    "question": "What is a retrieval gate and why is it important?",
    "expect_refusal": false,
    "notes": "Should explain gating and relate it to hallucination prevention."
  },
  {
    "id": "out_of_scope_01",
    "question": "What is the capital of France?",
    "expect_refusal": true,
    "notes": "If the knowledge base only includes our docs, the app should refuse."
  },
  {
    "id": "edge_01",
    "question": "",
    "expect_refusal": true,
    "notes": "Empty input should not call the LLM."
  }
]
</code></pre>
<h3 id="heading-step-2-create-evalsrunevalspy">Step 2: Create <code>evals/run_evals.py</code></h3>
<p>This runner calls your API endpoint (end-to-end) and checks expected behaviours.</p>
<pre><code class="language-python">import json
import requests

API_URL = "http://127.0.0.1:8000/answer"

def run():
    with open("evals/eval_set.json", "r", encoding="utf-8") as f:
        cases = json.load(f)

    passed = 0
    failed = 0

    for case in cases:
        resp = requests.post(API_URL, json={"question": case["question"]}, timeout=60)
        resp.raise_for_status()
        out = resp.json()

        got_refusal = bool(out.get("refusal", False))
        expect_refusal = bool(case["expect_refusal"])

        ok = (got_refusal == expect_refusal)

        # Beginner-friendly: if it answers, sources should exist and be a list
        if not got_refusal:
            ok = ok and isinstance(out.get("sources"), list)

        if ok:
            passed += 1
            print(f"PASS {case['id']}")
        else:
            failed += 1
            print(f"FAIL {case['id']} expected_refusal={expect_refusal} got_refusal={got_refusal}")
            print("Output:", json.dumps(out, indent=2))

    print(f"\nDone. Passed={passed} Failed={failed}")
    if failed:
        raise SystemExit(1)

if __name__ == "__main__":
    run()
</code></pre>
<h3 id="heading-how-to-use-evals-in-practice">How to Use Evals in Practice</h3>
<p>Run your server:</p>
<pre><code class="language-python">uvicorn app:app --reload
</code></pre>
<p>In another terminal, run evals:</p>
<pre><code class="language-python">python evals/run_evals.py
</code></pre>
<p>If an eval fails, you have a concrete signal that something changed in retrieval, gating, prompting, or provider behaviour.</p>
<h2 id="heading-what-to-improve-next-realistic-upgrades">What to Improve Next: Realistic Upgrades</h2>
<p>Building a reliable RAG app is iterative. Here are realistic next steps:</p>
<ul>
<li><p><strong>Semantic chunking:</strong> Break text based on meaning instead of character count.</p>
</li>
<li><p><strong>Reranking:</strong> Use a cross-encoder reranker to reorder the top-k chunks for higher precision.</p>
</li>
<li><p><strong>Metadata filtering:</strong> Filter results by category, date, or department to reduce false positives.</p>
</li>
<li><p><strong>Better citations:</strong> Store chunk IDs and show exactly which chunk(s) the answer came from.</p>
</li>
<li><p><strong>Observability:</strong> Add request IDs, structured logs, and traces so “what happened?” is answerable.</p>
</li>
<li><p><strong>Async + background indexing:</strong> Move index building to a background job and keep the API responsive.</p>
</li>
</ul>
<h2 id="heading-final-thoughts-production-ready-is-a-set-of-habits">Final Thoughts: Production-Ready Is a Set of Habits</h2>
<p>Building an AI application that survives in the real world is about building a system that is predictable, measurable, and safe.</p>
<ul>
<li><p><strong>Retrieval quality is measurable:</strong> Use similarity scores to gate your LLM.</p>
</li>
<li><p><strong>Refusal is a feature:</strong> It is better to say “I do not know” than to lie.</p>
</li>
<li><p><strong>Fallbacks are mandatory:</strong> Design for the moment the API goes down.</p>
</li>
<li><p><strong>Evals prevent regressions:</strong> Never deploy a change without running your tests.</p>
</li>
</ul>
<h2 id="heading-about-me">About Me</h2>
<p>I am Chidozie Managwu, an award-winning AI Product Architect and founder focused on helping global tech talent build real, production-ready skills. I contribute to global AI initiatives as a GAFAI Delegate and lead AI Titans Network, a community for developers learning how to ship AI products.</p>
<p>My work has been recognized with the Global Tech Hero award and featured on platforms like HackerNoon.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Rate Limiter with Redis and Python to Scale Your Apps ]]>
                </title>
                <description>
                    <![CDATA[ If you've ever built a web application, you know that without a proper mechanism to control traffic, your application can become overwhelmed, leading to slow response times, server crashes, and a poor user experience. Even worse, it can leave you vul... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-rate-limiter-with-redis-and-python/</link>
                <guid isPermaLink="false">68dfe6e0dcc5f825f4d48c85</guid>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cybersecurity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sravan Karuturi ]]>
                </dc:creator>
                <pubDate>Fri, 03 Oct 2025 15:08:16 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759503803144/4d974610-95dc-4db8-989a-0d705dc4d431.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you've ever built a web application, you know that without a proper mechanism to control traffic, your application can become overwhelmed, leading to slow response times, server crashes, and a poor user experience. Even worse, it can leave you vulnerable to Denial-of-Service (DoS) attacks. This is where rate limiting comes in.</p>
<p>In this tutorial, you’ll build a distributed rate limiter. This is the kind of system you need when your application is deployed across multiple servers or virtual machines, and you need to enforce a global limit on all incoming requests.</p>
<p>You’ll build a simple URL shortener application and then implement a robust rate limiter for it using a powerful and efficient combination of tools:</p>
<ul>
<li><p>Python and Flask for your web application.</p>
</li>
<li><p>Redis as your high-speed, centralized data store for tracking requests.</p>
</li>
<li><p>Terraform and Proxmox to define and provision your virtual machine infrastructure.</p>
</li>
<li><p>Docker to containerize your application for easy deployment.</p>
</li>
<li><p>Nginx as a load balancer to distribute traffic across your app servers.</p>
</li>
<li><p>k6 to load-test your system and prove that your rate limiter actually works.</p>
</li>
</ul>
<p>This is intended for new developers learning about various system design concepts or for experts who just want a refresher.</p>
<p>By the end of this guide, you'll understand not just the code, but the complete system architecture required to deploy a scalable, resilient application.</p>
<p>Let's get started!</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>While not absolutely required to follow along, I’d recommend setting up a Proxmox server on an old laptop to implement the topics you learn and code along with the article. I recommend this <a target="_blank" href="https://www.youtube.com/watch?v=5j0Zb6x_hOk&amp;list=PLT98CRl2KxKHnlbYhtABg6cF50bYa8Ulo">YouTube playlist</a> for getting started. Please note that I am in no way affiliated with this channel. I just found it helpful for me.</p>
<p>However, If you do not have a local Proxmox server, you can skip that part and just follow along to understand how a rate limiter is built and how it is set up to properly work with multiple servers.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-big-picture-our-system-architecture">The Big Picture: Our System Architecture</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-1-how-to-define-the-infrastructure-with-terraform">Step 1: How to Define the Infrastructure with Terraform</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-how-to-implement-the-rate-limiter-logic-in-python">Step 2: How to Implement the Rate Limiter Logic in Python</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-containerizing-and-testing">Step 3: Containerizing and Testing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-the-big-picture-our-system-architecture">The Big Picture: Our System Architecture</h2>
<p>Before we dive into the code, let's look at the architecture we're building. I will be using <a target="_blank" href="https://www.proxmox.com/en/products/proxmox-virtual-environment/overview">Proxmox Virtual Environment</a> to setup a server cluster just like you would have in a datacenter.</p>
<h3 id="heading-how-to-set-up-proxmox">How to Set Up Proxmox</h3>
<p><code>Proxmox Virtual Environment</code> is an open source platform for virtualization. It lets you manage multiple VMs, ccontainers and other clusters with ease. For instance, I turned my old gaming computer into a Proxmox server which lets me run more than 20 virtual machines on it at the same time, making it similar to my very own datacenter. This lets me experiment with distributed applications by simulating datacenter environments.</p>
<p>To setup your own cluster, all you need is an old computer. You can download the ISO image from <a target="_blank" href="https://www.proxmox.com/en/downloads">here</a> and boot from the USB drive. Once you install it, you can configure the host machine via a web browser on any other computer on the same network.</p>
<p>For example, my proxmox server is located at <code>10.0.0.108</code> and I can access it via the browser on my laptop.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759194790299/35e9363f-b739-4085-a589-c1bafbac0504.png" alt="Example Proxmox cluster" class="image--center mx-auto" width="3680" height="2206" loading="lazy"></p>
<p>We define all our virtual machines in our <code>main.tf</code> file. And run a simple command <code>terraform apply</code> to spin these servers up. For more reading on how to use Terraform with Proxmox, I recommend this <a target="_blank" href="https://spacelift.io/blog/terraform-proxmox-provider">blog post</a></p>
<p>Back to our use case, we’ll have a few virtual machines that will serve as different kinds of servers:</p>
<ol>
<li><p>A Load balancer</p>
</li>
<li><p>A Rate Limiter ( A Redis Cache )</p>
</li>
<li><p>Two Web Servers</p>
</li>
<li><p>A Postgres database</p>
</li>
<li><p>One Virtual Machine that will test the load by simulating hundreds of calls per minute.</p>
</li>
</ol>
<p>If all of this seems daunting, don’t worry too much about it. You don’t need to set all this up to follow along.</p>
<h3 id="heading-centralized-rate-limiter">Centralized Rate Limiter</h3>
<p>Since our application will run on multiple servers (or "nodes"), we can't store request counts in memory on each individual server. Why? Because each server would have its own separate count, and we wouldn't have a <em>global</em> rate limit.</p>
<p>The solution is to use a centralized data store that all our application nodes can access. This is where Redis comes in.</p>
<p>Here’s a diagram of our setup:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758476002871/1d70ce5b-e19c-4d7d-9c0b-cc18840a07bf.png" alt="A Small diagram depicting the architecture we'll form with all these virtual nodes" class="image--center mx-auto" width="2904" height="1173" loading="lazy"></p>
<ol>
<li><p>User requests first hit our Nginx load balancer.</p>
</li>
<li><p>The load balancer distributes the traffic evenly between our two web server VMs. The configuration is simple, using an upstream block to define the servers.</p>
</li>
<li><p>Each web server runs our Python Flask application inside a Docker container.</p>
</li>
<li><p>Before processing any request, the Flask app communicates with the central Redis rate limiter VM to check if the user has exceeded the rate limit.</p>
</li>
<li><p>If the user is within the limit, the app processes the request and interacts with the PostgreSQL Database. If they're over the limit, it sends back a “429 Too Many Requests” error.</p>
</li>
</ol>
<p>This architecture ensures that no matter which web server handles the request, the rate limit is checked against the same, shared data source.</p>
<h2 id="heading-step-1-how-to-define-the-infrastructure-with-terraform"><strong>Step 1: How to Define the Infrastructure with Terraform</strong></h2>
<p>Manually setting up multiple virtual machines can be tedious and prone to errors. That's why we use Terraform, an Infrastructure as Code (IaC) tool. It lets us define our entire infrastructure in configuration files.</p>
<p><strong>Note</strong>: You can skip this section if you just want to see the rate limiter in action and how it’s used.</p>
<p>Our <a target="_blank" href="https://github.com/sravankaruturi/system-design/blob/main/infra/main.tf">main.tf</a> file defines all the components of our system. Let's look at a key piece: the Redis VM.</p>
<pre><code class="lang-yaml"><span class="hljs-comment"># --- Redis Cache for Rate Limiter ---</span>
<span class="hljs-string">resource</span> <span class="hljs-string">"proxmox_vm_qemu"</span> <span class="hljs-string">"redis_cache"</span> {

    <span class="hljs-string">vmid</span>        <span class="hljs-string">=</span> <span class="hljs-number">130</span>
    <span class="hljs-string">name</span>        <span class="hljs-string">=</span> <span class="hljs-string">"redis-cache-rate-limiter"</span>
    <span class="hljs-string">target_node</span> <span class="hljs-string">=</span> <span class="hljs-string">"pve"</span>
    <span class="hljs-string">agent</span>       <span class="hljs-string">=</span> <span class="hljs-number">1</span>
    <span class="hljs-string">cores</span>       <span class="hljs-string">=</span> <span class="hljs-number">1</span>
    <span class="hljs-string">memory</span>      <span class="hljs-string">=</span> <span class="hljs-number">1024</span>
    <span class="hljs-comment"># ... cloud-init config ...</span>
    <span class="hljs-string">ipconfig0</span>  <span class="hljs-string">=</span> <span class="hljs-string">"ip=10.0.0.130/24,gw=10.0.0.1"</span>
    <span class="hljs-comment"># ... disk and network config ...</span>

    <span class="hljs-comment"># 1. Install Docker</span>
    <span class="hljs-string">provisioner</span> <span class="hljs-string">"remote-exec"</span> {
        <span class="hljs-string">inline</span> <span class="hljs-string">=</span> [
            <span class="hljs-string">"sleep 30; sudo apt-get update -y"</span>,
            <span class="hljs-string">"sudo apt-get install -y docker.io docker-compose"</span>,
            <span class="hljs-string">"sudo mkdir -p /opt/redis"</span>
        ]
    }

    <span class="hljs-comment"># 2. Upload docker-compose file</span>
    <span class="hljs-string">provisioner</span> <span class="hljs-string">"file"</span> {
         <span class="hljs-string">source</span>      <span class="hljs-string">=</span> <span class="hljs-string">"files/redis-docker-compose.yml"</span>
         <span class="hljs-string">destination</span> <span class="hljs-string">=</span> <span class="hljs-string">"/home/${var.ssh_user}/docker-compose.yml"</span>
    }

    <span class="hljs-comment"># 3. Move file and run docker-compose</span>
    <span class="hljs-string">provisioner</span> <span class="hljs-string">"remote-exec"</span> {
        <span class="hljs-string">inline</span> <span class="hljs-string">=</span> [
            <span class="hljs-string">"sudo mv /home/${var.ssh_user}/docker-compose.yml /opt/redis/docker-compose.yml"</span>,
            <span class="hljs-string">"cd /opt/redis &amp;&amp; sudo docker-compose up -d"</span>
        ]
    }
}
</code></pre>
<p>This block tells Terraform to create a <code>Proxmox QEMU virtual machine</code> with a specific IP address <code>(10.0.0.130)</code>. After the VM is created, it uses provisioners to connect via SSH and run commands. Here, it installs Docker, uploads our <code>redis-docker-compose.yml file</code>, and starts the Redis container.</p>
<p>The <code>redis-docker-compose.yml</code> itself is very straightforward:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.8'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">redis:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">redis:latest</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">redis_cache</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"6379:6379"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">redisdata:/data</span>

<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">redisdata:</span>
</code></pre>
<p>This ensures we have a persistent, containerized Redis instance ready to serve our application. The Terraform configuration similarly defines our web servers, load balancer, and databases.</p>
<h2 id="heading-step-2-how-to-implement-the-rate-limiter-logic-in-python"><strong>Step 2: How to Implement the Rate Limiter Logic in Python</strong></h2>
<p>Now, for the heart of our system: the Python code that implements the rate limiting logic. We're using a sophisticated and memory-efficient algorithm called the Sliding Window Log.</p>
<p>The idea is simple: for each user, we keep a log of the timestamps of their recent requests. We store this log in a Redis Sorted Set.</p>
<p>Let's break down the code from <a target="_blank" href="https://github.com/sravankaruturi/system-design/blob/main/web-servers/app.py"><code>app.py</code></a>.</p>
<h3 id="heading-the-flask-appbeforerequest-hook"><strong>The Flask</strong> <code>@app.before_request</code> <strong>Hook</strong></h3>
<p>Flask allows us to run code before any request is handled by its intended view function. This is the perfect place to put our rate limiter.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> psycopg2
<span class="hljs-keyword">import</span> string
<span class="hljs-keyword">import</span> random
<span class="hljs-keyword">import</span> redis
<span class="hljs-keyword">import</span> time
<span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask, request, redirect, jsonify

app = Flask(__name__)

<span class="hljs-comment"># --- Database Connection Details ---</span>
DB_HOST = <span class="hljs-string">"10.0.0.200"</span> 
DB_NAME = <span class="hljs-string">"urldb"</span>
DB_USER = <span class="hljs-string">"myuser"</span>
DB_PASS = <span class="hljs-string">"mypassword"</span>

REDIS_HOST = <span class="hljs-string">"10.0.0.130"</span> <span class="hljs-comment"># IP of your redis-cache-lxc</span>

<span class="hljs-comment"># --- Rate Limiter Settings ---</span>
RATE_LIMIT_COUNT = <span class="hljs-number">10</span>  <span class="hljs-comment"># 10 requests</span>
RATE_LIMIT_WINDOW = <span class="hljs-number">60</span> <span class="hljs-comment"># per 60 seconds</span>

<span class="hljs-comment"># Establish a reusable Redis connection</span>
redis_client = redis.Redis(host=REDIS_HOST, port=<span class="hljs-number">6379</span>, decode_responses=<span class="hljs-literal">True</span>)

<span class="hljs-meta">@app.before_request</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">rate_limiter</span>():</span>
    <span class="hljs-comment"># Use the user's IP address as the key</span>
    <span class="hljs-comment"># In a real app, you'd handle proxies via request.environ.get('HTTP_X_FORWARDED_FOR', request.remote_addr)</span>
    key = <span class="hljs-string">f"rate_limit:<span class="hljs-subst">{request.remote_addr}</span>"</span>
    now = time.time()

    <span class="hljs-comment"># Use a Redis pipeline for atomic operations</span>
    pipe = redis_client.pipeline()
    <span class="hljs-comment"># 1. Add current request timestamp. The score and member are the same.</span>
    pipe.zadd(key, {str(now): now})
    <span class="hljs-comment"># 2. Remove all timestamps older than our window</span>
    pipe.zremrangebyscore(key, <span class="hljs-number">0</span>, now - RATE_LIMIT_WINDOW)
    <span class="hljs-comment"># 3. Get the count of remaining timestamps</span>
    pipe.zcard(key)
    <span class="hljs-comment"># 4. Set an expiration on the key so it cleans itself up</span>
    pipe.expire(key, RATE_LIMIT_WINDOW)

    <span class="hljs-comment"># Execute the pipeline and get the results</span>
    results = pipe.execute()
    request_count = results[<span class="hljs-number">2</span>] <span class="hljs-comment"># The result of the zcard command</span>

    <span class="hljs-keyword">if</span> request_count &gt; RATE_LIMIT_COUNT:
        <span class="hljs-comment"># Return a 429 Too Many Requests error</span>
        <span class="hljs-keyword">return</span> jsonify(error=<span class="hljs-string">"Rate limit exceeded"</span>), <span class="hljs-number">429</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_db_connection</span>():</span>
    conn = psycopg2.connect(host=DB_HOST, dbname=DB_NAME, user=DB_USER, password=DB_PASS)
    <span class="hljs-keyword">return</span> conn

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">init_db</span>():</span>
    conn = get_db_connection()
    cur = conn.cursor()
    cur.execute(<span class="hljs-string">'''
        CREATE TABLE IF NOT EXISTS urls (
            id SERIAL PRIMARY KEY,
            short_code VARCHAR(6) UNIQUE NOT NULL,
            original_url TEXT NOT NULL
        );
    '''</span>)
    <span class="hljs-comment"># Check if the index exists before creating it</span>
    cur.execute(<span class="hljs-string">'''
        SELECT 1 FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
        WHERE c.relname = 'idx_original_url' AND n.nspname = 'public';
    '''</span>)
    <span class="hljs-keyword">if</span> cur.fetchone() <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
        cur.execute(<span class="hljs-string">'CREATE INDEX idx_original_url ON urls (original_url);'</span>)
    conn.commit()
    cur.close()
    conn.close()

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">generate_short_code</span>(<span class="hljs-params">length=<span class="hljs-number">6</span></span>):</span>
    chars = string.ascii_letters + string.digits
    <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>.join(random.choice(chars) <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(length))

<span class="hljs-meta">@app.route("/", methods=['GET'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">index</span>():</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"URL Shortener is running!\n"</span>, <span class="hljs-number">200</span>

<span class="hljs-meta">@app.route('/shorten', methods=['POST'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">shorten_url</span>():</span>
    original_url = request.form[<span class="hljs-string">'url'</span>]
    conn = get_db_connection()
    cur = conn.cursor()

    cur.execute(<span class="hljs-string">"SELECT short_code FROM urls WHERE original_url = %s"</span>, (original_url,))
    existing_url = cur.fetchone()

    <span class="hljs-keyword">if</span> existing_url:
        short_code = existing_url[<span class="hljs-number">0</span>]
    <span class="hljs-keyword">else</span>:
        short_code = generate_short_code()
        cur.execute(<span class="hljs-string">"INSERT INTO urls (short_code, original_url) VALUES (%s, %s)"</span>, (short_code, original_url))
        conn.commit()

    cur.close()
    conn.close()

    <span class="hljs-keyword">return</span> jsonify(short_url=<span class="hljs-string">f"/<span class="hljs-subst">{short_code}</span>"</span>)

<span class="hljs-meta">@app.route('/&lt;short_code&gt;')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">redirect_to_url</span>(<span class="hljs-params">short_code</span>):</span>
    conn = get_db_connection()
    cur = conn.cursor()
    cur.execute(<span class="hljs-string">"SELECT original_url FROM urls WHERE short_code = %s"</span>, (short_code,))
    url_record = cur.fetchone()
    cur.close()
    conn.close()

    <span class="hljs-keyword">if</span> url_record:
        <span class="hljs-keyword">return</span> redirect(url_record[<span class="hljs-number">0</span>])
    <span class="hljs-keyword">else</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"URL not found"</span>, <span class="hljs-number">404</span>

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    init_db() 
    app.run(host=<span class="hljs-string">'0.0.0.0'</span>, port=<span class="hljs-number">5000</span>)
</code></pre>
<h3 id="heading-how-it-works-step-by-step"><strong>How It Works, Step-by-Step</strong></h3>
<ol>
<li><p><strong>Identify the User:</strong> We create a unique Redis key for each user based on their IP address: <code>rate_limit:1.2.3.4</code>.</p>
</li>
<li><p><strong>Use a Pipeline:</strong> Network latency can be a bottleneck. A Redis pipeline bundles multiple commands into a single request-response cycle. This is much more efficient than sending them one by one. It also ensures the sequence of commands runs without being interrupted by commands from other clients.</p>
</li>
<li><p><strong>Log the Current Request (ZADD):</strong> We add the current timestamp (as a Unix epoch) to a sorted set. We use the timestamp for both the "member" and the "score," which allows us to easily filter by time.</p>
</li>
<li><p><strong>Clean Up Old Requests (ZREMRANGEBYSCORE):</strong> This is the "sliding window" part. We remove any timestamps from the set that are older than our <code>RATE_LIMIT_WINDOW</code> (60 seconds). This efficiently discards requests that are no longer relevant to the current rate limit period.</p>
</li>
<li><p><strong>Count the Recent Requests (ZCARD):</strong> We get the cardinality (the number of items) in the set. After the previous step, this number is our count of requests within the last 60 seconds.</p>
</li>
<li><p><strong>Mark the current record to expire (EXPIRE):</strong> We set an expiration on the key itself. If a user stops making requests, Redis will automatically delete their rate limit data after 60 seconds, preventing memory from filling up with old keys.</p>
</li>
<li><p><strong>Execute and Check:</strong> The <code>pipe.execute()</code> command sends all our bundled commands to Redis. We then check the result of our ZCARD command. If the count exceeds our <code>RATE_LIMIT_COUNT</code>, we immediately return a 429 error.</p>
</li>
</ol>
<p>This approach is incredibly fast and efficient. All the heavy lifting is done inside Redis, which is optimized for these kinds of operations.</p>
<h2 id="heading-step-3-containerizing-and-testing"><strong>Step 3: Containerizing and Testing</strong></h2>
<p>To deploy our application consistently across multiple VMs, we use Docker. Our Dockerfile is standard for a Python application: it starts from a Python image, installs dependencies from <code>requirements.txt</code>, copies the application code, and defines the command to run the app.</p>
<p>But how do we know it works? We test it!</p>
<p>We use <code>k6</code>, a modern load testing tool, to simulate heavy traffic. Our test script, <code>rate-test.js</code>, is designed specifically to verify the rate limiter.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> http <span class="hljs-keyword">from</span> <span class="hljs-string">'k6/http'</span>;
<span class="hljs-keyword">import</span> { check, sleep } <span class="hljs-keyword">from</span> <span class="hljs-string">'k6'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> options = {
  <span class="hljs-attr">stages</span>: [
    <span class="hljs-comment">// Ramp up to 20 users. This is more than the 10 req/min limit</span>
    <span class="hljs-comment">// and should trigger the rate limiter.</span>
    { <span class="hljs-attr">duration</span>: <span class="hljs-string">'30s'</span>, <span class="hljs-attr">target</span>: <span class="hljs-number">20</span> },
    { <span class="hljs-attr">duration</span>: <span class="hljs-string">'1m'</span>, <span class="hljs-attr">target</span>: <span class="hljs-number">20</span> },
    { <span class="hljs-attr">duration</span>: <span class="hljs-string">'10s'</span>, <span class="hljs-attr">target</span>: <span class="hljs-number">0</span> },
  ],
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> url = <span class="hljs-string">'http://10.0.0.100/shorten'</span>; <span class="hljs-comment">// The Load Balancer IP</span>
  <span class="hljs-keyword">const</span> payload = { <span class="hljs-attr">url</span>: <span class="hljs-string">`https://www.test-ratelimit-<span class="hljs-subst">${<span class="hljs-built_in">Math</span>.random()}</span>.com`</span> };

  <span class="hljs-keyword">const</span> res = http.post(url, payload);

  <span class="hljs-comment">// Check if the request was successful OR if it was correctly rate-limited</span>
  check(res, {
    <span class="hljs-string">'status is 200 (OK)'</span>: <span class="hljs-function">(<span class="hljs-params">r</span>) =&gt;</span> r.status === <span class="hljs-number">200</span>,
    <span class="hljs-string">'status is 429 (Too Many Requests)'</span>: <span class="hljs-function">(<span class="hljs-params">r</span>) =&gt;</span> r.status === <span class="hljs-number">429</span>,
  });

  sleep(<span class="hljs-number">1</span>);
}
</code></pre>
<p>The stages array configures the test to gradually increase the number of virtual users to 20. Since our rate limit is 10 requests per minute, this load is guaranteed to trigger the limiter.</p>
<p>The <code>check</code> function is the crucial part. It verifies that the server's response code is either 200 (meaning the request was successful) or 429 (meaning our rate limiter correctly blocked the request).</p>
<p>We should see about 10 of our requests go through of the around 1600 requests per minute that we send from the same IP address.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758477504110/3a2f3f0f-8db0-453d-8900-42a6d0966a11.gif" alt="A gif showing the test run of the load testing script" class="image--center mx-auto" width="2640" height="1416" loading="lazy"></p>
<p>We can also check the logs on our webserver to see all the requests that were sent to it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758477959201/80a39d07-1c4e-4d45-8a42-9ac2ce6f360d.gif" alt="A small gif demonstrating Web Server Logs" class="image--center mx-auto" width="2640" height="1416" loading="lazy"></p>
<p>And if we look at the Redis cache/database itself, we’ll see all the keys and the TTL at which they expire.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758478780827/6a07a2ee-0ad0-4b60-899f-d6a0453edbe7.png" alt="6a07a2ee-0ad0-4b60-899f-d6a0453edbe7" class="image--center mx-auto" width="3246" height="1834" loading="lazy"></p>
<p>This is how we rate limit applications using a Redis Cache Server.</p>
<p>Here are the complete files used in the project.</p>
<pre><code class="lang-yaml">    <span class="hljs-string">terraform</span> {
    <span class="hljs-string">required_providers</span> {
        <span class="hljs-string">proxmox</span> <span class="hljs-string">=</span> {
        <span class="hljs-string">source</span>  <span class="hljs-string">=</span> <span class="hljs-string">"telmate/proxmox"</span>
        <span class="hljs-string">version</span> <span class="hljs-string">=</span> <span class="hljs-string">"3.0.2-rc04"</span>
        }
    }
    }

    <span class="hljs-string">provider</span> <span class="hljs-string">"proxmox"</span> {
    <span class="hljs-string">pm_api_url</span>          <span class="hljs-string">=</span> <span class="hljs-string">var.proxmox_api_url</span>
    <span class="hljs-string">pm_api_token_id</span>     <span class="hljs-string">=</span> <span class="hljs-string">var.proxmox_api_token_id</span>
    <span class="hljs-string">pm_api_token_secret</span> <span class="hljs-string">=</span> <span class="hljs-string">var.proxmox_api_token_secret</span>
    <span class="hljs-string">pm_tls_insecure</span>     <span class="hljs-string">=</span> <span class="hljs-literal">true</span>
    }

    <span class="hljs-comment"># --- Shared Provisioner Connection Settings ---</span>
    <span class="hljs-string">locals</span> {
        <span class="hljs-string">connection_settings</span> <span class="hljs-string">=</span> {
            <span class="hljs-string">type</span>        <span class="hljs-string">=</span> <span class="hljs-string">"ssh"</span>
            <span class="hljs-string">user</span>        <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
            <span class="hljs-string">private_key</span> <span class="hljs-string">=</span> <span class="hljs-string">file(var.ssh_private_key_path)</span>
        }
    }

    <span class="hljs-comment"># --- Database LXC Containers ---</span>
    <span class="hljs-string">resource</span> <span class="hljs-string">"proxmox_lxc"</span> <span class="hljs-string">"postgres_db"</span> {
    <span class="hljs-string">hostname</span>     <span class="hljs-string">=</span> <span class="hljs-string">"postgres-db-lxc"</span>
    <span class="hljs-string">target_node</span>  <span class="hljs-string">=</span> <span class="hljs-string">var.target_node</span>
    <span class="hljs-string">ostemplate</span>   <span class="hljs-string">=</span> <span class="hljs-string">var.lxc_template</span>

    <span class="hljs-string">rootfs</span> {
        <span class="hljs-string">storage</span> <span class="hljs-string">=</span> <span class="hljs-string">"local-lvm"</span>
        <span class="hljs-string">size</span> <span class="hljs-string">=</span> <span class="hljs-string">"8G"</span>
    }

    <span class="hljs-string">password</span>     <span class="hljs-string">=</span> <span class="hljs-string">"admin"</span>
    <span class="hljs-string">unprivileged</span> <span class="hljs-string">=</span> <span class="hljs-literal">true</span>
    <span class="hljs-string">start</span>        <span class="hljs-string">=</span> <span class="hljs-literal">true</span>

    <span class="hljs-string">features</span> {
        <span class="hljs-string">nesting</span> <span class="hljs-string">=</span> <span class="hljs-literal">true</span>
        <span class="hljs-comment"># keyctl = true</span>
    }

    <span class="hljs-string">network</span> {
        <span class="hljs-string">name</span>   <span class="hljs-string">=</span> <span class="hljs-string">"eth0"</span>
        <span class="hljs-string">bridge</span> <span class="hljs-string">=</span> <span class="hljs-string">"vmbr0"</span>
        <span class="hljs-string">ip</span>     <span class="hljs-string">=</span> <span class="hljs-string">"10.0.0.200/24"</span>
        <span class="hljs-string">gw</span>     <span class="hljs-string">=</span> <span class="hljs-string">"10.0.0.1"</span>
    }

    <span class="hljs-string">provisioner</span> <span class="hljs-string">"remote-exec"</span> {
        <span class="hljs-string">connection</span> {
        <span class="hljs-string">type</span>        <span class="hljs-string">=</span> <span class="hljs-string">"ssh"</span>
        <span class="hljs-string">user</span>        <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
        <span class="hljs-string">private_key</span> <span class="hljs-string">=</span> <span class="hljs-string">file(var.ssh_private_key_path)</span>
        <span class="hljs-string">host</span>        <span class="hljs-string">=</span> <span class="hljs-string">split("/"</span>, <span class="hljs-string">self.network</span>[<span class="hljs-number">0</span>]<span class="hljs-string">.ip)</span>[<span class="hljs-number">0</span>]
        }
        <span class="hljs-string">inline</span> <span class="hljs-string">=</span> [
        <span class="hljs-string">"sudo apt-get update"</span>,
        <span class="hljs-string">"sudo apt-get install -y docker.io docker-compose python3-setuptools"</span>,
        <span class="hljs-string">"sudo usermod -aG docker ${var.ssh_user}"</span>,
        <span class="hljs-string">"sudo mkdir -p /opt/postgres"</span>,
        <span class="hljs-string">"sudo chown ${var.ssh_user}:${var.ssh_user} /opt/postgres"</span>
        ]
    }

    <span class="hljs-string">provisioner</span> <span class="hljs-string">"file"</span> {
        <span class="hljs-string">connection</span> {
        <span class="hljs-string">type</span>        <span class="hljs-string">=</span> <span class="hljs-string">"ssh"</span>
        <span class="hljs-string">user</span>        <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
        <span class="hljs-string">private_key</span> <span class="hljs-string">=</span> <span class="hljs-string">file(var.ssh_private_key_path)</span>
        <span class="hljs-string">host</span>        <span class="hljs-string">=</span> <span class="hljs-string">split("/"</span>, <span class="hljs-string">self.network</span>[<span class="hljs-number">0</span>]<span class="hljs-string">.ip)</span>[<span class="hljs-number">0</span>]
        }
        <span class="hljs-string">source</span>      <span class="hljs-string">=</span> <span class="hljs-string">"../databases/pg-docker-compose.yml"</span>
        <span class="hljs-string">destination</span> <span class="hljs-string">=</span> <span class="hljs-string">"/opt/postgres/docker-compose.yml"</span>
    }

    <span class="hljs-string">provisioner</span> <span class="hljs-string">"remote-exec"</span> {
        <span class="hljs-string">inline</span>     <span class="hljs-string">=</span> [<span class="hljs-string">"cd /opt/postgres &amp;&amp; sudo docker-compose up -d"</span>]

        <span class="hljs-string">connection</span> {
        <span class="hljs-string">type</span>        <span class="hljs-string">=</span> <span class="hljs-string">"ssh"</span>
        <span class="hljs-string">user</span>        <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
        <span class="hljs-string">private_key</span> <span class="hljs-string">=</span> <span class="hljs-string">file(var.ssh_private_key_path)</span>
        <span class="hljs-string">host</span>        <span class="hljs-string">=</span> <span class="hljs-string">split("/"</span>, <span class="hljs-string">self.network</span>[<span class="hljs-number">0</span>]<span class="hljs-string">.ip)</span>[<span class="hljs-number">0</span>]
        }
    }
    }

    <span class="hljs-string">resource</span> <span class="hljs-string">"proxmox_lxc"</span> <span class="hljs-string">"mongo_db"</span> {
        <span class="hljs-string">hostname</span>    <span class="hljs-string">=</span> <span class="hljs-string">"mongo-db-lxc"</span>
        <span class="hljs-string">target_node</span> <span class="hljs-string">=</span> <span class="hljs-string">var.target_node</span>
        <span class="hljs-string">ostemplate</span>  <span class="hljs-string">=</span> <span class="hljs-string">var.lxc_template</span>

        <span class="hljs-string">rootfs</span> {
            <span class="hljs-string">storage</span> <span class="hljs-string">=</span> <span class="hljs-string">"local-lvm"</span>
            <span class="hljs-string">size</span> <span class="hljs-string">=</span> <span class="hljs-string">"8G"</span>
        }

        <span class="hljs-string">password</span>    <span class="hljs-string">=</span> <span class="hljs-string">"admin"</span>
        <span class="hljs-string">unprivileged</span> <span class="hljs-string">=</span> <span class="hljs-literal">true</span>
        <span class="hljs-string">start</span>       <span class="hljs-string">=</span> <span class="hljs-literal">true</span>

        <span class="hljs-string">features</span> {
            <span class="hljs-string">nesting</span> <span class="hljs-string">=</span> <span class="hljs-literal">true</span>
        <span class="hljs-comment"># keyctl = true # Somehow this is blocking the apply command</span>
        }

        <span class="hljs-string">network</span> {
            <span class="hljs-string">name</span>   <span class="hljs-string">=</span> <span class="hljs-string">"eth0"</span>
            <span class="hljs-string">bridge</span> <span class="hljs-string">=</span> <span class="hljs-string">"vmbr0"</span>
            <span class="hljs-string">ip</span>     <span class="hljs-string">=</span> <span class="hljs-string">"10.0.0.210/24"</span>
            <span class="hljs-string">gw</span>     <span class="hljs-string">=</span> <span class="hljs-string">"10.0.0.1"</span>
        }

        <span class="hljs-comment"># Provisioners similar to postgres_db</span>
        <span class="hljs-string">provisioner</span> <span class="hljs-string">"remote-exec"</span> {
            <span class="hljs-string">connection</span> {
                <span class="hljs-string">type</span>        <span class="hljs-string">=</span> <span class="hljs-string">"ssh"</span>
                <span class="hljs-string">user</span>        <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
                <span class="hljs-string">private_key</span> <span class="hljs-string">=</span> <span class="hljs-string">file(var.ssh_private_key_path)</span>
                <span class="hljs-string">host</span>        <span class="hljs-string">=</span> <span class="hljs-string">split("/"</span>, <span class="hljs-string">self.network</span>[<span class="hljs-number">0</span>]<span class="hljs-string">.ip)</span>[<span class="hljs-number">0</span>]
            }
            <span class="hljs-string">inline</span> <span class="hljs-string">=</span> [
            <span class="hljs-string">"sudo apt-get update"</span>,
            <span class="hljs-string">"sudo apt-get install -y docker.io docker-compose python3-setuptools"</span>,
            <span class="hljs-string">"sudo usermod -aG docker ${var.ssh_user}"</span>,
            <span class="hljs-string">"sudo mkdir -p /opt/mongo"</span>,
            <span class="hljs-string">"sudo chown ${var.ssh_user}:${var.ssh_user} /opt/mongo"</span>
            ]
        }

        <span class="hljs-string">provisioner</span> <span class="hljs-string">"file"</span> {
            <span class="hljs-string">connection</span> {
            <span class="hljs-string">type</span>        <span class="hljs-string">=</span> <span class="hljs-string">"ssh"</span>
            <span class="hljs-string">user</span>        <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
            <span class="hljs-string">private_key</span> <span class="hljs-string">=</span> <span class="hljs-string">file(var.ssh_private_key_path)</span>
            <span class="hljs-string">host</span>        <span class="hljs-string">=</span> <span class="hljs-string">split("/"</span>, <span class="hljs-string">self.network</span>[<span class="hljs-number">0</span>]<span class="hljs-string">.ip)</span>[<span class="hljs-number">0</span>]
            }
            <span class="hljs-string">source</span>      <span class="hljs-string">=</span> <span class="hljs-string">"../databases/mongo-docker-compose.yml"</span>
            <span class="hljs-string">destination</span> <span class="hljs-string">=</span> <span class="hljs-string">"/opt/mongo/docker-compose.yml"</span>
        }

        <span class="hljs-string">provisioner</span> <span class="hljs-string">"remote-exec"</span> {
            <span class="hljs-string">connection</span> {
            <span class="hljs-string">type</span>        <span class="hljs-string">=</span> <span class="hljs-string">"ssh"</span>
            <span class="hljs-string">user</span>        <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
            <span class="hljs-string">private_key</span> <span class="hljs-string">=</span> <span class="hljs-string">file(var.ssh_private_key_path)</span>
            <span class="hljs-string">host</span>        <span class="hljs-string">=</span> <span class="hljs-string">split("/"</span>, <span class="hljs-string">self.network</span>[<span class="hljs-number">0</span>]<span class="hljs-string">.ip)</span>[<span class="hljs-number">0</span>]
            }
            <span class="hljs-string">inline</span>     <span class="hljs-string">=</span> [<span class="hljs-string">"cd /opt/mongo &amp;&amp; docker-compose up -d"</span>]
        }
    }

    <span class="hljs-comment"># --- Redis Cache for Rate Limiter ---</span>
    <span class="hljs-string">resource</span> <span class="hljs-string">"proxmox_vm_qemu"</span> <span class="hljs-string">"redis_cache"</span> {

        <span class="hljs-string">vmid</span>        <span class="hljs-string">=</span> <span class="hljs-number">130</span>
        <span class="hljs-string">name</span>        <span class="hljs-string">=</span> <span class="hljs-string">"redis-cache-rate-limiter"</span>
        <span class="hljs-string">target_node</span> <span class="hljs-string">=</span> <span class="hljs-string">"pve"</span>
        <span class="hljs-string">agent</span>       <span class="hljs-string">=</span> <span class="hljs-number">1</span>
        <span class="hljs-string">cpu</span> {
            <span class="hljs-string">cores</span>       <span class="hljs-string">=</span> <span class="hljs-number">1</span>
        }

        <span class="hljs-string">memory</span>      <span class="hljs-string">=</span> <span class="hljs-number">1024</span>
        <span class="hljs-string">boot</span>        <span class="hljs-string">=</span> <span class="hljs-string">"order=scsi0"</span> <span class="hljs-comment"># has to be the same as the OS disk of the template</span>
        <span class="hljs-string">clone</span>       <span class="hljs-string">=</span> <span class="hljs-string">"debian12-cloudinit"</span> <span class="hljs-comment"># The name of the template</span>
        <span class="hljs-string">scsihw</span>      <span class="hljs-string">=</span> <span class="hljs-string">"virtio-scsi-single"</span>
        <span class="hljs-string">vm_state</span>    <span class="hljs-string">=</span> <span class="hljs-string">"running"</span>
        <span class="hljs-string">automatic_reboot</span> <span class="hljs-string">=</span> <span class="hljs-literal">true</span>

        <span class="hljs-comment"># Cloud-Init configuration</span>
        <span class="hljs-string">cicustom</span>   <span class="hljs-string">=</span> <span class="hljs-string">"vendor=local:snippets/qemu-guest-agent.yml"</span> <span class="hljs-comment"># /var/lib/vz/snippets/qemu-guest-agent.yml</span>
        <span class="hljs-string">ciupgrade</span>  <span class="hljs-string">=</span> <span class="hljs-literal">true</span>
        <span class="hljs-string">nameserver</span> <span class="hljs-string">=</span> <span class="hljs-string">"1.1.1.1 8.8.8.8"</span>
        <span class="hljs-string">ipconfig0</span>  <span class="hljs-string">=</span> <span class="hljs-string">"ip=10.0.0.130/24,gw=10.0.0.1"</span>
        <span class="hljs-string">skip_ipv6</span>  <span class="hljs-string">=</span> <span class="hljs-literal">true</span>
        <span class="hljs-string">ciuser</span>     <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
        <span class="hljs-string">cipassword</span> <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_password</span>
        <span class="hljs-string">sshkeys</span>    <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_key</span>

        <span class="hljs-comment"># Most cloud-init images require a serial device for their display</span>
        <span class="hljs-string">serial</span> {
            <span class="hljs-string">id</span> <span class="hljs-string">=</span> <span class="hljs-number">0</span>
        }

        <span class="hljs-string">disks</span> {
            <span class="hljs-string">scsi</span> {
            <span class="hljs-string">scsi0</span> {
                <span class="hljs-comment"># We have to specify the disk from our template, else Terraform will think it's not supposed to be there</span>
                <span class="hljs-string">disk</span> {
                <span class="hljs-string">storage</span> <span class="hljs-string">=</span> <span class="hljs-string">"local-lvm"</span>
                <span class="hljs-comment"># The size of the disk should be at least as big as the disk in the template. If it's smaller, the disk will be recreated</span>
                <span class="hljs-string">size</span>    <span class="hljs-string">=</span> <span class="hljs-string">"5G"</span> 
                }
            }
            }
            <span class="hljs-string">ide</span> {
            <span class="hljs-comment"># Some images require a cloud-init disk on the IDE controller, others on the SCSI or SATA controller</span>
            <span class="hljs-string">ide1</span> {
                <span class="hljs-string">cloudinit</span> {
                <span class="hljs-string">storage</span> <span class="hljs-string">=</span> <span class="hljs-string">"local-lvm"</span>
                }
            }
            }
        }

        <span class="hljs-string">network</span> {
            <span class="hljs-string">id</span> <span class="hljs-string">=</span> <span class="hljs-number">0</span>
            <span class="hljs-string">bridge</span> <span class="hljs-string">=</span> <span class="hljs-string">"vmbr0"</span>
            <span class="hljs-string">model</span>  <span class="hljs-string">=</span> <span class="hljs-string">"virtio"</span>
        }

        <span class="hljs-string">connection</span> {
            <span class="hljs-string">type</span>        <span class="hljs-string">=</span> <span class="hljs-string">"ssh"</span>
            <span class="hljs-string">user</span>        <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
            <span class="hljs-string">private_key</span> <span class="hljs-string">=</span> <span class="hljs-string">file(var.ssh_private_key_path)</span>
            <span class="hljs-string">host</span>        <span class="hljs-string">=</span> <span class="hljs-string">"10.0.0.130"</span>
        }

        <span class="hljs-comment"># 1. Install Docker and create the final app directory</span>
        <span class="hljs-string">provisioner</span> <span class="hljs-string">"remote-exec"</span> {
            <span class="hljs-string">inline</span> <span class="hljs-string">=</span> [
                <span class="hljs-comment"># Wait for cloud-init to finish before doing anything else</span>
                <span class="hljs-string">"echo 'Waiting for cloud-init to finish...'"</span>,
                <span class="hljs-string">"while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Still waiting...' &amp;&amp; sleep 1; done"</span>,
                <span class="hljs-string">"echo 'Cloud-init finished.'"</span>,

                <span class="hljs-comment"># Now, safely install packages</span>
                <span class="hljs-string">"sudo apt-get update -y"</span>,
                <span class="hljs-string">"sudo apt-get install -y docker.io docker-compose"</span>,
                <span class="hljs-string">"sudo mkdir -p /opt/redis"</span>,
            ]
        }

        <span class="hljs-string">provisioner</span> <span class="hljs-string">"file"</span> {
            <span class="hljs-string">source</span>      <span class="hljs-string">=</span> <span class="hljs-string">"../caching/redis-docker-compose.yml"</span>
            <span class="hljs-string">destination</span> <span class="hljs-string">=</span> <span class="hljs-string">"/home/${var.ssh_user}/docker-compose.yml"</span>
        }

        <span class="hljs-string">provisioner</span> <span class="hljs-string">"remote-exec"</span> {
            <span class="hljs-string">inline</span> <span class="hljs-string">=</span> [ <span class="hljs-string">"sudo mv /home/${var.ssh_user}/docker-compose.yml /opt/redis/docker-compose.yml"</span> ]
        }

        <span class="hljs-string">provisioner</span> <span class="hljs-string">"remote-exec"</span> {
            <span class="hljs-string">inline</span> <span class="hljs-string">=</span> [ <span class="hljs-string">"cd /opt/redis &amp;&amp; sudo docker-compose up -d"</span> ]
        }
    }

    <span class="hljs-string">resource</span> <span class="hljs-string">"proxmox_vm_qemu"</span> <span class="hljs-string">"web-servers"</span> {

        <span class="hljs-string">count</span> <span class="hljs-string">=</span> <span class="hljs-number">2</span>

        <span class="hljs-string">vmid</span>        <span class="hljs-string">=</span> <span class="hljs-string">count.index</span> <span class="hljs-string">+</span> <span class="hljs-number">150</span>
        <span class="hljs-string">name</span>        <span class="hljs-string">=</span> <span class="hljs-string">"web-server-tf-${count.index + 1}"</span>
        <span class="hljs-string">target_node</span> <span class="hljs-string">=</span> <span class="hljs-string">"pve"</span>
        <span class="hljs-string">agent</span>       <span class="hljs-string">=</span> <span class="hljs-number">1</span>
        <span class="hljs-string">cpu</span> {
            <span class="hljs-string">cores</span>       <span class="hljs-string">=</span> <span class="hljs-number">1</span>
        }
        <span class="hljs-string">memory</span>      <span class="hljs-string">=</span> <span class="hljs-number">1024</span>
        <span class="hljs-string">boot</span>        <span class="hljs-string">=</span> <span class="hljs-string">"order=scsi0"</span> <span class="hljs-comment"># has to be the same as the OS disk of the template</span>
        <span class="hljs-string">clone</span>       <span class="hljs-string">=</span> <span class="hljs-string">"debian12-cloudinit"</span> <span class="hljs-comment"># The name of the template</span>
        <span class="hljs-string">scsihw</span>      <span class="hljs-string">=</span> <span class="hljs-string">"virtio-scsi-single"</span>
        <span class="hljs-string">vm_state</span>    <span class="hljs-string">=</span> <span class="hljs-string">"running"</span>
        <span class="hljs-string">automatic_reboot</span> <span class="hljs-string">=</span> <span class="hljs-literal">true</span>

        <span class="hljs-comment"># Cloud-Init configuration</span>
        <span class="hljs-string">cicustom</span>   <span class="hljs-string">=</span> <span class="hljs-string">"vendor=local:snippets/qemu-guest-agent.yml"</span> <span class="hljs-comment"># /var/lib/vz/snippets/qemu-guest-agent.yml</span>
        <span class="hljs-string">ciupgrade</span>  <span class="hljs-string">=</span> <span class="hljs-literal">true</span>
        <span class="hljs-string">nameserver</span> <span class="hljs-string">=</span> <span class="hljs-string">"1.1.1.1 8.8.8.8"</span>
        <span class="hljs-string">ipconfig0</span>  <span class="hljs-string">=</span> <span class="hljs-string">"ip=10.0.0.${111 + count.index}/24,gw=10.0.0.1"</span>
        <span class="hljs-string">skip_ipv6</span>  <span class="hljs-string">=</span> <span class="hljs-literal">true</span>
        <span class="hljs-string">ciuser</span>     <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
        <span class="hljs-string">cipassword</span> <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_password</span>
        <span class="hljs-string">sshkeys</span>    <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_key</span>

        <span class="hljs-comment"># Most cloud-init images require a serial device for their display</span>
        <span class="hljs-string">serial</span> {
            <span class="hljs-string">id</span> <span class="hljs-string">=</span> <span class="hljs-number">0</span>
        }

        <span class="hljs-string">disks</span> {
            <span class="hljs-string">scsi</span> {
            <span class="hljs-string">scsi0</span> {
                <span class="hljs-comment"># We have to specify the disk from our template, else Terraform will think it's not supposed to be there</span>
                <span class="hljs-string">disk</span> {
                <span class="hljs-string">storage</span> <span class="hljs-string">=</span> <span class="hljs-string">"local-lvm"</span>
                <span class="hljs-comment"># The size of the disk should be at least as big as the disk in the template. If it's smaller, the disk will be recreated</span>
                <span class="hljs-string">size</span>    <span class="hljs-string">=</span> <span class="hljs-string">"5G"</span> 
                }
            }
            }
            <span class="hljs-string">ide</span> {
            <span class="hljs-comment"># Some images require a cloud-init disk on the IDE controller, others on the SCSI or SATA controller</span>
            <span class="hljs-string">ide1</span> {
                <span class="hljs-string">cloudinit</span> {
                <span class="hljs-string">storage</span> <span class="hljs-string">=</span> <span class="hljs-string">"local-lvm"</span>
                }
            }
            }
        }

        <span class="hljs-string">network</span> {
            <span class="hljs-string">id</span> <span class="hljs-string">=</span> <span class="hljs-number">0</span>
            <span class="hljs-string">bridge</span> <span class="hljs-string">=</span> <span class="hljs-string">"vmbr0"</span>
            <span class="hljs-string">model</span>  <span class="hljs-string">=</span> <span class="hljs-string">"virtio"</span>
        }

        <span class="hljs-string">connection</span> {
            <span class="hljs-string">type</span>        <span class="hljs-string">=</span> <span class="hljs-string">"ssh"</span>
            <span class="hljs-string">user</span>        <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
            <span class="hljs-string">private_key</span> <span class="hljs-string">=</span> <span class="hljs-string">file(var.ssh_private_key_path)</span>
            <span class="hljs-string">host</span>        <span class="hljs-string">=</span> <span class="hljs-string">"10.0.0.${111 + count.index}"</span>
        }

        <span class="hljs-comment"># 1. Install Docker and create the final app directory</span>
        <span class="hljs-string">provisioner</span> <span class="hljs-string">"remote-exec"</span> {
            <span class="hljs-string">inline</span> <span class="hljs-string">=</span> [
                <span class="hljs-comment"># Wait for cloud-init to finish before doing anything else</span>
                <span class="hljs-string">"echo 'Waiting for cloud-init to finish...'"</span>,
                <span class="hljs-string">"while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Still waiting...' &amp;&amp; sleep 1; done"</span>,
                <span class="hljs-string">"echo 'Cloud-init finished.'"</span>,

                <span class="hljs-comment"># Now, safely install packages</span>
                <span class="hljs-string">"sudo apt-get update -y"</span>,
                <span class="hljs-string">"sudo apt-get install -y docker.io"</span>,
                <span class="hljs-string">"sudo mkdir -p /opt/app"</span>,
            ]
        }

        <span class="hljs-comment"># 2. Upload ONLY the necessary files to the user's home directory</span>
        <span class="hljs-string">provisioner</span> <span class="hljs-string">"file"</span> {
            <span class="hljs-string">source</span>      <span class="hljs-string">=</span> <span class="hljs-string">"../web-servers/app.py"</span>
            <span class="hljs-string">destination</span> <span class="hljs-string">=</span> <span class="hljs-string">"/home/${var.ssh_user}/app.py"</span>
        }
        <span class="hljs-string">provisioner</span> <span class="hljs-string">"file"</span> {
            <span class="hljs-string">source</span>      <span class="hljs-string">=</span> <span class="hljs-string">"../web-servers/Dockerfile"</span>
            <span class="hljs-string">destination</span> <span class="hljs-string">=</span> <span class="hljs-string">"/home/${var.ssh_user}/Dockerfile"</span>
        }
        <span class="hljs-string">provisioner</span> <span class="hljs-string">"file"</span> {
            <span class="hljs-string">source</span>      <span class="hljs-string">=</span> <span class="hljs-string">"../web-servers/requirements.txt"</span>
            <span class="hljs-string">destination</span> <span class="hljs-string">=</span> <span class="hljs-string">"/home/${var.ssh_user}/requirements.txt"</span>
        }

        <span class="hljs-comment"># 4. Move files from the home directory, build the image, and run the container</span>
        <span class="hljs-string">provisioner</span> <span class="hljs-string">"remote-exec"</span> {
            <span class="hljs-string">inline</span> <span class="hljs-string">=</span> [
                <span class="hljs-comment"># Move each file individually to be compatible with all shells</span>
                <span class="hljs-string">"sudo mv /home/${var.ssh_user}/app.py /opt/app/"</span>,
                <span class="hljs-string">"sudo mv /home/${var.ssh_user}/Dockerfile /opt/app/"</span>,
                <span class="hljs-string">"sudo mv /home/${var.ssh_user}/requirements.txt /opt/app/"</span>,

                <span class="hljs-comment"># Build the Docker image</span>
                <span class="hljs-string">"sudo docker build -t my-python-app /opt/app"</span>,

                <span class="hljs-comment"># Stop and remove any old containers to prevent conflicts</span>
                <span class="hljs-string">"sudo docker stop $(sudo docker ps -q --filter ancestor=my-python-app) 2&gt;/dev/null || true"</span>,
                <span class="hljs-string">"sudo docker rm $(sudo docker ps -aq --filter ancestor=my-python-app) 2&gt;/dev/null || true"</span>,

                <span class="hljs-comment"># Run the new container</span>
                <span class="hljs-string">"sudo docker run -d --restart always -p 80:5000 my-python-app"</span>
            ]
        }

        <span class="hljs-comment"># In your proxmox_vm_qemu "web_servers" resource</span>
        <span class="hljs-string">depends_on</span> <span class="hljs-string">=</span> [
            <span class="hljs-string">proxmox_lxc.postgres_db</span>,
            <span class="hljs-string">proxmox_vm_qemu.redis_cache</span>
        ]
    }

    <span class="hljs-comment"># --- Load Balancer VM ---</span>
    <span class="hljs-string">resource</span> <span class="hljs-string">"proxmox_vm_qemu"</span> <span class="hljs-string">"load_balancer"</span> {
        <span class="hljs-string">name</span>        <span class="hljs-string">=</span> <span class="hljs-string">"lb-1"</span>
        <span class="hljs-string">target_node</span> <span class="hljs-string">=</span> <span class="hljs-string">var.target_node</span>
        <span class="hljs-string">clone</span>       <span class="hljs-string">=</span> <span class="hljs-string">var.vm_template</span>
        <span class="hljs-string">agent</span>       <span class="hljs-string">=</span> <span class="hljs-number">1</span>
        <span class="hljs-string">cpu</span> {
            <span class="hljs-string">cores</span>       <span class="hljs-string">=</span> <span class="hljs-number">1</span>
        }
        <span class="hljs-string">memory</span>      <span class="hljs-string">=</span> <span class="hljs-number">512</span>
        <span class="hljs-string">boot</span>        <span class="hljs-string">=</span> <span class="hljs-string">"order=scsi0"</span> <span class="hljs-comment"># has to be the same as the OS disk of the template</span>
        <span class="hljs-string">scsihw</span>      <span class="hljs-string">=</span> <span class="hljs-string">"virtio-scsi-single"</span>
        <span class="hljs-string">vm_state</span>    <span class="hljs-string">=</span> <span class="hljs-string">"running"</span>
        <span class="hljs-string">automatic_reboot</span> <span class="hljs-string">=</span> <span class="hljs-literal">true</span>

        <span class="hljs-comment"># --- Add these lines for Cloud Init Drive ---</span>
                <span class="hljs-comment"># --- Add these lines for Cloud Init Drive ---</span>
        <span class="hljs-string">cicustom</span>   <span class="hljs-string">=</span> <span class="hljs-string">"vendor=local:snippets/qemu-guest-agent.yml"</span> <span class="hljs-comment"># /var/lib/vz/snippets/qemu-guest-agent.yml</span>
        <span class="hljs-string">ciupgrade</span>  <span class="hljs-string">=</span> <span class="hljs-literal">true</span>
        <span class="hljs-string">nameserver</span> <span class="hljs-string">=</span> <span class="hljs-string">"1.1.1.1 8.8.8.8"</span>
        <span class="hljs-string">ipconfig0</span>  <span class="hljs-string">=</span> <span class="hljs-string">"ip=10.0.0.100/24,gw=10.0.0.1"</span>
        <span class="hljs-string">skip_ipv6</span>  <span class="hljs-string">=</span> <span class="hljs-literal">true</span>
        <span class="hljs-string">ciuser</span>     <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
        <span class="hljs-string">cipassword</span> <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_password</span>
        <span class="hljs-string">sshkeys</span>    <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_key</span>

        <span class="hljs-comment"># Most cloud-init images require a serial device for their display</span>
        <span class="hljs-string">serial</span> {
            <span class="hljs-string">id</span> <span class="hljs-string">=</span> <span class="hljs-number">0</span>
        }

        <span class="hljs-string">disks</span> {
            <span class="hljs-string">scsi</span> {
            <span class="hljs-string">scsi0</span> {
                <span class="hljs-comment"># We have to specify the disk from our template, else Terraform will think it's not supposed to be there</span>
                <span class="hljs-string">disk</span> {
                <span class="hljs-string">storage</span> <span class="hljs-string">=</span> <span class="hljs-string">"local-lvm"</span>
                <span class="hljs-comment"># The size of the disk should be at least as big as the disk in the template. If it's smaller, the disk will be recreated</span>
                <span class="hljs-string">size</span>    <span class="hljs-string">=</span> <span class="hljs-string">"5G"</span> 
                }
            }
            }
            <span class="hljs-string">ide</span> {
            <span class="hljs-comment"># Some images require a cloud-init disk on the IDE controller, others on the SCSI or SATA controller</span>
            <span class="hljs-string">ide1</span> {
                <span class="hljs-string">cloudinit</span> {
                <span class="hljs-string">storage</span> <span class="hljs-string">=</span> <span class="hljs-string">"local-lvm"</span>
                }
            }
            }
        }

        <span class="hljs-string">network</span> {
            <span class="hljs-string">id</span> <span class="hljs-string">=</span> <span class="hljs-number">0</span>
            <span class="hljs-string">bridge</span> <span class="hljs-string">=</span> <span class="hljs-string">"vmbr0"</span>
            <span class="hljs-string">model</span>  <span class="hljs-string">=</span> <span class="hljs-string">"virtio"</span>
        }

        <span class="hljs-string">connection</span> {
            <span class="hljs-string">type</span>        <span class="hljs-string">=</span> <span class="hljs-string">"ssh"</span>
            <span class="hljs-string">user</span>        <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
            <span class="hljs-string">private_key</span> <span class="hljs-string">=</span> <span class="hljs-string">file(var.ssh_private_key_path)</span>
            <span class="hljs-string">host</span>        <span class="hljs-string">=</span> <span class="hljs-string">"10.0.0.100"</span>
        }

        <span class="hljs-comment"># Step 1: Install Nginx</span>
        <span class="hljs-string">provisioner</span> <span class="hljs-string">"remote-exec"</span> {
            <span class="hljs-string">inline</span> <span class="hljs-string">=</span> [
                <span class="hljs-comment"># Wait for cloud-init to finish before doing anything else</span>
                <span class="hljs-string">"echo 'Waiting for cloud-init to finish...'"</span>,
                <span class="hljs-string">"while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Still waiting...' &amp;&amp; sleep 1; done"</span>,
                <span class="hljs-string">"echo 'Cloud-init finished.'"</span>,

                <span class="hljs-comment"># Now, safely install packages</span>
                <span class="hljs-string">"sudo apt-get update -y"</span>,
                <span class="hljs-string">"sudo apt-get install -y nginx"</span>
            ]
        }

        <span class="hljs-comment"># Step 2: Upload config to a temporary location</span>
        <span class="hljs-string">provisioner</span> <span class="hljs-string">"file"</span> {
            <span class="hljs-string">source</span>      <span class="hljs-string">=</span> <span class="hljs-string">"../web-servers/nginx.conf"</span>
            <span class="hljs-string">destination</span> <span class="hljs-string">=</span> <span class="hljs-string">"/tmp/nginx.conf"</span> <span class="hljs-comment"># Use /tmp instead</span>
        }

        <span class="hljs-comment"># Step 3: Use sudo to move the file to its final destination and reload nginx</span>
        <span class="hljs-string">provisioner</span> <span class="hljs-string">"remote-exec"</span> {
            <span class="hljs-string">inline</span> <span class="hljs-string">=</span> [
                <span class="hljs-string">"sudo mv /tmp/nginx.conf /etc/nginx/sites-available/default"</span>,
                <span class="hljs-string">"sudo systemctl reload nginx"</span>
            ]
        }
    }


    <span class="hljs-comment"># --- Load Tester VM ---</span>
    <span class="hljs-string">resource</span> <span class="hljs-string">"proxmox_vm_qemu"</span> <span class="hljs-string">"load_tester"</span> {
        <span class="hljs-string">name</span>        <span class="hljs-string">=</span> <span class="hljs-string">"load-tester-vm"</span>
        <span class="hljs-string">target_node</span> <span class="hljs-string">=</span> <span class="hljs-string">var.target_node</span>
        <span class="hljs-string">clone</span>       <span class="hljs-string">=</span> <span class="hljs-string">var.vm_template</span>
        <span class="hljs-string">agent</span>       <span class="hljs-string">=</span> <span class="hljs-number">1</span>
        <span class="hljs-string">cpu</span> {
            <span class="hljs-string">cores</span>       <span class="hljs-string">=</span> <span class="hljs-number">1</span>
        }
        <span class="hljs-string">memory</span>      <span class="hljs-string">=</span> <span class="hljs-number">1024</span>
        <span class="hljs-string">boot</span>        <span class="hljs-string">=</span> <span class="hljs-string">"order=scsi0"</span> <span class="hljs-comment"># has to be the same as the OS disk of the template</span>
        <span class="hljs-string">scsihw</span>      <span class="hljs-string">=</span> <span class="hljs-string">"virtio-scsi-single"</span>
        <span class="hljs-string">vm_state</span>    <span class="hljs-string">=</span> <span class="hljs-string">"running"</span>
        <span class="hljs-string">automatic_reboot</span> <span class="hljs-string">=</span> <span class="hljs-literal">true</span>

        <span class="hljs-comment"># --- Add these lines for Cloud Init Drive ---</span>
        <span class="hljs-string">cicustom</span>   <span class="hljs-string">=</span> <span class="hljs-string">"vendor=local:snippets/qemu-guest-agent.yml"</span> <span class="hljs-comment"># /var/lib/vz/snippets/qemu-guest-agent.yml</span>
        <span class="hljs-string">ciupgrade</span>  <span class="hljs-string">=</span> <span class="hljs-literal">true</span>
        <span class="hljs-string">nameserver</span> <span class="hljs-string">=</span> <span class="hljs-string">"1.1.1.1 8.8.8.8"</span>
        <span class="hljs-string">ipconfig0</span>  <span class="hljs-string">=</span> <span class="hljs-string">"ip=10.0.0.160/24,gw=10.0.0.1"</span>
        <span class="hljs-string">skip_ipv6</span>  <span class="hljs-string">=</span> <span class="hljs-literal">true</span>
        <span class="hljs-string">ciuser</span>     <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
        <span class="hljs-string">cipassword</span> <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_password</span>
        <span class="hljs-string">sshkeys</span>    <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_key</span>

        <span class="hljs-comment"># Most cloud-init images require a serial device for their display</span>
        <span class="hljs-string">serial</span> {
            <span class="hljs-string">id</span> <span class="hljs-string">=</span> <span class="hljs-number">0</span>
        }

        <span class="hljs-string">disks</span> {
            <span class="hljs-string">scsi</span> {
                <span class="hljs-string">scsi0</span> {
                    <span class="hljs-comment"># We have to specify the disk from our template, else Terraform will think it's not supposed to be there</span>
                    <span class="hljs-string">disk</span> {
                    <span class="hljs-string">storage</span> <span class="hljs-string">=</span> <span class="hljs-string">"local-lvm"</span>
                    <span class="hljs-comment"># The size of the disk should be at least as big as the disk in the template. If it's smaller, the disk will be recreated</span>
                    <span class="hljs-string">size</span>    <span class="hljs-string">=</span> <span class="hljs-string">"5G"</span> 
                    }
                }
            }

            <span class="hljs-string">ide</span> {
            <span class="hljs-comment"># Some images require a cloud-init disk on the IDE controller, others on the SCSI or SATA controller</span>
                <span class="hljs-string">ide1</span> {
                    <span class="hljs-string">cloudinit</span> {
                    <span class="hljs-string">storage</span> <span class="hljs-string">=</span> <span class="hljs-string">"local-lvm"</span>
                    }
                }
            }
        }

        <span class="hljs-string">network</span> {
            <span class="hljs-string">id</span> <span class="hljs-string">=</span> <span class="hljs-number">0</span>
            <span class="hljs-string">bridge</span> <span class="hljs-string">=</span> <span class="hljs-string">"vmbr0"</span>
            <span class="hljs-string">model</span>  <span class="hljs-string">=</span> <span class="hljs-string">"virtio"</span>
        }

        <span class="hljs-string">provisioner</span> <span class="hljs-string">"remote-exec"</span> {
            <span class="hljs-string">connection</span> {
                <span class="hljs-string">type</span>        <span class="hljs-string">=</span> <span class="hljs-string">"ssh"</span>
                <span class="hljs-string">user</span>        <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
                <span class="hljs-string">private_key</span> <span class="hljs-string">=</span> <span class="hljs-string">file(var.ssh_private_key_path)</span>
                <span class="hljs-string">host</span>        <span class="hljs-string">=</span> <span class="hljs-string">"10.0.0.160"</span>
            }
            <span class="hljs-string">inline</span> <span class="hljs-string">=</span> [
                <span class="hljs-comment"># Wait for cloud-init to finish</span>
                <span class="hljs-string">"echo 'Waiting for cloud-init to finish...'"</span>,
                <span class="hljs-string">"while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Still waiting...' &amp;&amp; sleep 1; done"</span>,
                <span class="hljs-string">"echo 'Cloud-init finished.'"</span>,

                <span class="hljs-comment"># Install prerequisites</span>
                <span class="hljs-string">"sudo apt-get update -y"</span>,
                <span class="hljs-string">"sudo apt-get install -y gnupg curl"</span>,

                <span class="hljs-comment"># Add the k6 repository and key</span>
                <span class="hljs-string">"curl -sL https://dl.k6.io/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/k6-archive-keyring.gpg"</span>,
                <span class="hljs-string">"echo 'deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main' | sudo tee /etc/apt/sources.list.d/k6.list"</span>,

                <span class="hljs-comment"># Install k6</span>
                <span class="hljs-string">"sudo apt-get update"</span>,
                <span class="hljs-string">"sudo apt-get install -y k6"</span>
            ]
        }

        <span class="hljs-string">provisioner</span> <span class="hljs-string">"file"</span> {
            <span class="hljs-string">connection</span> {
            <span class="hljs-string">type</span>        <span class="hljs-string">=</span> <span class="hljs-string">"ssh"</span>
            <span class="hljs-string">user</span>        <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
            <span class="hljs-string">private_key</span> <span class="hljs-string">=</span> <span class="hljs-string">file(var.ssh_private_key_path)</span>
            <span class="hljs-string">host</span>        <span class="hljs-string">=</span> <span class="hljs-string">"10.0.0.160"</span>
            }
            <span class="hljs-string">source</span>      <span class="hljs-string">=</span> <span class="hljs-string">"../load-testing/script.js"</span>
            <span class="hljs-string">destination</span> <span class="hljs-string">=</span> <span class="hljs-string">"/home/${var.ssh_user}/script.js"</span>
        }

        <span class="hljs-string">provisioner</span> <span class="hljs-string">"file"</span> {
            <span class="hljs-string">connection</span> {
            <span class="hljs-string">type</span>        <span class="hljs-string">=</span> <span class="hljs-string">"ssh"</span>
            <span class="hljs-string">user</span>        <span class="hljs-string">=</span> <span class="hljs-string">var.ssh_user</span>
            <span class="hljs-string">private_key</span> <span class="hljs-string">=</span> <span class="hljs-string">file(var.ssh_private_key_path)</span>
            <span class="hljs-string">host</span>        <span class="hljs-string">=</span> <span class="hljs-string">"10.0.0.160"</span>
            }
            <span class="hljs-string">source</span>      <span class="hljs-string">=</span> <span class="hljs-string">"../load-testing/rate-test.js"</span>
            <span class="hljs-string">destination</span> <span class="hljs-string">=</span> <span class="hljs-string">"/home/${var.ssh_user}/rate-test.js"</span>
        }

    }
</code></pre>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>You've now seen how to build a complete, scalable, and resilient system that includes a crucial component for modern web applications: a distributed rate limiter.</p>
<p>We've covered the entire stack:</p>
<ul>
<li><p><strong>Infrastructure as Code</strong> with Terraform to define our virtual machines. (check out my repo <a target="_blank" href="https://github.com/sravankaruturi/system-design">here</a> for all the code and any updates I make).</p>
</li>
<li><p>A <strong>centralized, high-speed cache</strong> with Redis to store our rate limiting data.</p>
</li>
<li><p>An efficient <strong>Sliding Window Log algorithm</strong> implemented in Python with Flask.</p>
</li>
<li><p><strong>Containerization</strong> with Docker for consistent deployment.</p>
</li>
<li><p><strong>Load balancing</strong> with Nginx to distribute traffic.</p>
</li>
<li><p><strong>Load testing</strong> with k6 to validate our implementation.</p>
</li>
</ul>
<p>If you’d like to learn more of the concepts that are used when building large scale systems please follow me at <a class="user-mention" href="https://hashnode.com/@sravankaruturi">Sravan Karuturi</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Send Emails in Python using Mailtrap SMTP and the Email API ]]>
                </title>
                <description>
                    <![CDATA[ In this tutorial, I’ll walk you through the process of sending emails in Python using two different methods:  The traditional SMTP setup with the built-in ‘smtplib’ module.  Mailtrap email API via Mailtrap’s official SDK.  If you’re unfamiliar wi... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/send-emails-in-python-using-mailtrap-smtp-and-the-email-api/</link>
                <guid isPermaLink="false">67e6ab43aa64aee164e7985e</guid>
                
                    <category>
                        <![CDATA[ Phyton ]]>
                    </category>
                
                    <category>
                        <![CDATA[ smtp ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mailtrap ]]>
                    </category>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Alex Tray ]]>
                </dc:creator>
                <pubDate>Fri, 28 Mar 2025 13:59:31 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743110284000/6fb2a037-ddca-4625-acfb-cffbd167ec55.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this tutorial, I’ll walk you through the process of sending emails in Python using two different methods: </p>
<ol>
<li><p>The traditional SMTP setup with the built-in ‘smtplib’ module. </p>
</li>
<li><p>Mailtrap email API via Mailtrap’s official SDK. </p>
</li>
</ol>
<p>If you’re unfamiliar with the tools and workflows, SMTP (Simple Mail Transfer Protocol) is the protocol commonly used for sending emails via apps and websites. Mailtrap is an email delivery platform designed for high deliverability with growth-focused features and industry-best analytics. </p>
<p>By the end of the article, you’ll understand how to integrate email-sending capabilities into Python projects and use Mailtrap for reliable email delivery in real-world scenarios.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-smtplib-setup">'smtplib' Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-send-emails-with-mailtrap-smtp">How to Send emails with Mailtrap SMTP</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-send-emails-with-the-mailtrap-email-api">How to Send emails with the Mailtrap Email API</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
</ol>
<h2 id="heading-smtplib-setup">‘smtplib’ Setup</h2>
<p>To start sending emails with Python, I'll first use the built-in ‘smtplib’ module. This lets you connect to an SMTP server and send emails directly from your app. </p>
<p>So, start by importing the ‘smtplib’ module with the statement below:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> smtplib
</code></pre>
<p>Next, create an ‘SMTP’ object to configure the connection to your SMTP server. This object handles the email sending. </p>
<pre><code class="lang-python">smtpObj = smtplib.SMTP(host, port)
</code></pre>
<ul>
<li><p>‘host’ refers to the SMTP server endpoint, such as ‘live.smtp.mailtrap.io’</p>
</li>
<li><p>‘port’ is the communication channel used by the server. The recommended port is usually 587 for secure email sending with TLS encryption. </p>
</li>
</ul>
<p><strong>Pro tip</strong>: An SMTP object has a ‘sendmail’ instance object with three parameters, where each parameter is a string (‘receivers’ is a list of strings). </p>
<pre><code class="lang-python">smtpObj.sendmail(sender, receivers, message)
</code></pre>
<p>If you want to ensure you’ve properly imported the ‘smtplib’ module and check the full description of arguments and classes, run the following command:</p>
<pre><code class="lang-python">help(smtplib)
</code></pre>
<h2 id="heading-how-to-send-emails-with-mailtrap-smtp">How to Send emails with Mailtrap SMTP</h2>
<p>This method involves setting up the custom SMTP credentials you get for Mailtrap.</p>
<p><strong>Important notes</strong>: </p>
<ul>
<li><p><strong>Testing out the service with Mailtrap’s dummy domain</strong> – To try Mailtrap, you don’t need to verify your domain right away. You can use Mailtrap’s dummy domain (you get access to it when you sign up), which allows you to simulate sending emails without worrying about the DNS records. This is ideal for testing the service and getting familiar with Mailtrap’s features.  </p>
</li>
<li><p><strong>Domain verification for production</strong> – If you plan to send real emails to recipients, you’ll need to verify your domain. This involves adding DNS records such as SPF, DKIM, and <a target="_blank" href="https://dmarcreport.com/">DMARC</a> to your domain provider’s DNS settings. These records ensure your emails are delivered successfully and help protect against phishing and spoofing. In the next section, I'll show you how to set these up in your domain provider's dashboard. </p>
</li>
</ul>
<h3 id="heading-verify-your-sending-domain-spf-dkim-and-dmarc">Verify your sending domain (SPF, DKIM, and DMARC)</h3>
<p>DNS records are critical to ensure your emails are delivered successfully, and mailbox providers such as Gmail and Yahoo require DNS authentication. </p>
<p>But before we go through a quick tutorial on how to do it, let’s review each type of record so you understand why they’re so important:</p>
<ul>
<li><p><strong>SPF (Sender Policy Framework)</strong>: The record helps mail servers determine if the sender’s IP address is authorized to send emails from your domain. Simply, adding an SPF record prevents spammers from sending emails that appear to come from your domain. </p>
</li>
<li><p><strong>DKIM (DomainKeys Identified Mail)</strong>: DKIM uses encryption to verify the sender's domain and ensures that the email content hasn't been tampered with during transmission. This protects your emails from being spoofed. </p>
</li>
<li><p><strong>DMARC (Domain-based Message Authentication, Reporting &amp; Conformance)</strong>: DMARC ties SPF and DKIM together, providing a policy for handling unauthenticated emails and reporting on email activities. In a nutshell, it gives you more control over your domain’s email security. </p>
</li>
</ul>
<p>Now, here’s how to add the records: </p>
<ol>
<li><p>First, you need to access your domain provider's DNS settings. Usually, you can access them in the domain register or domain settings. For example, GoDaddy calls the menu Manage DNS, and it's dubbed similarly with other providers. </p>
</li>
<li><p>Next, add (copy-paste) the DNS records Mailtrap provides into your domain provider's DNS settings. Note that Mailtrap's records are read-made, and SPF is pre-parsed, so you don't need to create anything additional – just add the records. </p>
</li>
</ol>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfHx2AAc87krxYh7twU5Ypuz-Iu6gklvJeVBzpdgptvfc7B9g7X3BBnqWai8n47HTDJrj1rZ2ny0jfscJJYgAAFcuEsZeVqYO2OellzvQgaXMjnMMxIeOoPGF0ildRbecEi7rjPbg?key=CJmzmKUWxlFjIw3A041wXvaj" alt="Screenshot showing domain verification" width="1600" height="1175" loading="lazy"></p>
<ol start="3">
<li>Finally, you can check the status of your records with Mailtrap. </li>
</ol>
<p>Below is the bare-bones script for sending emails via Mailtrap using Python. For security reasons, the script uses placeholder credentials for the username and password (except for the SMTP server endpoint and port).</p>
<p>When running the script, be sure to replace these placeholders with your actual Mailtrap credentials to ensure the email is sent successfully. </p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> smtplib
<span class="hljs-keyword">from</span> email.mime.text <span class="hljs-keyword">import</span> MIMEText

<span class="hljs-comment"># Configuration</span>
port = <span class="hljs-number">587</span>
smtp_server = <span class="hljs-string">"live.smtp.mailtrap.io"</span>
login = <span class="hljs-string">"api"</span>  <span class="hljs-comment"># Your login generated by Mailtrap</span>
password = <span class="hljs-string">"1a2b3c4d5e6f7g"</span>  <span class="hljs-comment"># Your password generated by Mailtrap</span>

sender_email = <span class="hljs-string">"mailtrap@example.com"</span>
receiver_email = <span class="hljs-string">"new@example.com"</span>

<span class="hljs-comment"># Plain text content</span>
text = <span class="hljs-string">"""\
Hi,
Check out the new post on the Mailtrap blog:
SMTP Server for Testing: Cloud-based or Local?
https://blog.mailtrap.io/2018/09/27/cloud-or-local-smtp-server/
Feel free to let us know what content would be useful for you!
"""</span>

<span class="hljs-comment"># Create MIMEText object</span>
message = MIMEText(text, <span class="hljs-string">"plain"</span>)
message[<span class="hljs-string">"Subject"</span>] = <span class="hljs-string">"Plain text email"</span>
message[<span class="hljs-string">"From"</span>] = sender_email
message[<span class="hljs-string">"To"</span>] = receiver_email

<span class="hljs-comment"># Send the email</span>
<span class="hljs-keyword">with</span> smtplib.SMTP(smtp_server, port) <span class="hljs-keyword">as</span> server:
    server.starttls()  <span class="hljs-comment"># Secure the connection</span>
    server.login(login, password)
    server.sendmail(sender_email, receiver_email, message.as_string())

print(<span class="hljs-string">'Sent'</span>)
</code></pre>
<p><strong>In the script</strong>:</p>
<ul>
<li><p>The ‘smtplib’ and ‘MIMEText’ modules have been imported from Python’s library. </p>
</li>
<li><p>As mentioned, SMTP server configuration needs to be updated with your credentials. But the server endpoint and port are as is. </p>
</li>
<li><p>Since this is a bare-bones script, I used ‘MIMEText’, which holds ‘plaintext’ only. But the script can be easily refactored to use ‘MIMEMultipart’ for both ‘plaintext’ and ‘HTML’. Jump to the quick tut below to see how it’s done. </p>
</li>
<li><p>When sending the email, I chose to use the ‘with’ statement (context manager) to ensure the SMTP server connection gets closed right after the email gets sent. </p>
</li>
</ul>
<p><strong>Security tip</strong>: </p>
<p>Server information and the login credentials shouldn't be hardcoded into your sending script. When setting the script for production, make sure you use environment variables to store sensitive information. This makes the code more secure and more flexible, particularly when you move it between different dev stages. For example ⬇️</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os

smtp_server = os.getenv(<span class="hljs-string">"SMTP_SERVER"</span>, <span class="hljs-string">"default.smtp.server"</span>)
login = os.getenv(<span class="hljs-string">"SMTP_LOGIN"</span>)
password = os.getenv(<span class="hljs-string">"SMTP_PASSWORD"</span>)

<span class="hljs-comment"># Example usage in an SMTP connection setup</span>
<span class="hljs-comment"># smtp.login(login, password)</span>
</code></pre>
<p>Note that you need to set the variables in your operating system prior to running the script. </p>
<h3 id="heading-refactor-the-script-to-use-html-emails">Refactor the script to use HTML emails</h3>
<p>HTML emails provide a better user experience. They allow you to include formatted text, images, tables, clickable links, and custom styling. This works great for marketing emails, newsletters, or any communication where design and branding matter. </p>
<p>So, to refactor the script, you would import ‘MIMEMultipart’ and ‘MIMEText’. This action allows you to customize the HTML emails yet keep the plain-text versions as a fallback if your recipients cannot open the HTML email. </p>
<p>Here’s the revised script:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> smtplib
<span class="hljs-keyword">from</span> email.mime.multipart <span class="hljs-keyword">import</span> MIMEMultipart
<span class="hljs-keyword">from</span> email.mime.text <span class="hljs-keyword">import</span> MIMEText

<span class="hljs-comment"># Configuration</span>
smtp_server = <span class="hljs-string">"live.smtp.mailtrap.io"</span>
port = <span class="hljs-number">587</span>
login = <span class="hljs-string">"api"</span>  <span class="hljs-comment"># Mailtrap login</span>
password = <span class="hljs-string">"1a2b3c4d5e6f7g"</span>  <span class="hljs-comment"># Mailtrap password</span>

sender_email = <span class="hljs-string">"mailtrap@example.com"</span>
receiver_email = <span class="hljs-string">"new@example.com"</span>

message = MIMEMultipart()
message[<span class="hljs-string">"From"</span>] = sender_email
message[<span class="hljs-string">"To"</span>] = receiver_email
message[<span class="hljs-string">"Subject"</span>] = <span class="hljs-string">"HTML Email"</span>

<span class="hljs-comment"># Add plain text content (optional, for email clients that don't render HTML)</span>
message.attach(MIMEText(<span class="hljs-string">"This is a plain text version of the email."</span>, <span class="hljs-string">"plain"</span>))

<span class="hljs-comment"># Add HTML content</span>
html_content = <span class="hljs-string">"""\
&lt;html&gt;
  &lt;body&gt;
    &lt;h1&gt;Welcome to Mailtrap!&lt;/h1&gt;
    &lt;p&gt;This is an example of an HTML email.&lt;/p&gt;
  &lt;/body&gt;
&lt;/html&gt;
"""</span>
message.attach(MIMEText(html_content, <span class="hljs-string">"html"</span>))

<span class="hljs-comment"># Send the email</span>
<span class="hljs-keyword">with</span> smtplib.SMTP(smtp_server, port) <span class="hljs-keyword">as</span> server:
    server.starttls()
    server.login(login, password)
    server.sendmail(sender_email, receiver_email, message.as_string())

print(<span class="hljs-string">'Sent'</span>)
</code></pre>
<p>Lastly, I’ve included video instructions for the SMTP method – so if that works better for you, feel free to check it out 🔽. </p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=ufLpTc9up8s&amp;t=1s">How to send email in Python using Mailtrap - Tutorial by Mailtrap</a></p>
<h2 id="heading-how-to-send-emails-with-the-mailtrap-email-api">How to Send emails with the Mailtrap email API</h2>
<p>If you're looking to move beyond using SMTP for sending emails and want to integrate Mailtrap’s email API into your Python applications, this section will walk you through how to do that. </p>
<p>The Mailtrap <a target="_blank" href="https://mailtrap.io/smtp-api/">SMTP email API</a> allows you to send emails more efficiently, with added flexibility and scalability. Before starting, make sure you have a verified sending domain on Mailtrap and the Mailtrap API token, which you’ll use to authenticate requests.</p>
<p><strong>Note</strong>: I’m covering the API integration using the official Mailtrap Python SDK. </p>
<p>So, first you install the official SDK with the command below. </p>
<pre><code class="lang-python">pip install mailtrap
</code></pre>
<p><strong>Prerequisite</strong>: Ensure your Python package version is 3.6+ or higher. </p>
<p>After installing the SDK, the next step is to create a Mail object. This object will represent the email you want to send, including essential details like the sender, recipient, subject, and email content. </p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> mailtrap <span class="hljs-keyword">as</span> mt

<span class="hljs-comment"># Create the mail object</span>
mail = mt.Mail(
    sender=mt.Address(email=<span class="hljs-string">"mailtrap@example.com"</span>, name=<span class="hljs-string">"Mailtrap Test"</span>),  <span class="hljs-comment"># Sender info</span>
    to=[mt.Address(email=<span class="hljs-string">"your@email.com"</span>)],  <span class="hljs-comment"># Recipient info</span>
    subject=<span class="hljs-string">"You are awesome!"</span>,  <span class="hljs-comment"># Email subject</span>
    text=<span class="hljs-string">"Congrats for sending a test email with Mailtrap!"</span>  <span class="hljs-comment"># Email content (plain text)</span>
)

<span class="hljs-comment"># Create a client using your API key</span>
client = mt.MailtrapClient(token=<span class="hljs-string">"your-api-key"</span>)

<span class="hljs-comment"># Send the email</span>
client.send(mail)
</code></pre>
<p><strong>Quick notes:</strong></p>
<ul>
<li><p><strong>Sender and recipient</strong>: You need to specify the sender’s email address, which must match your verified domain. Similarly, define the recipient's email.</p>
</li>
<li><p><strong>Subject and text content</strong>: Set the subject and plain text content of the email. You can also add HTML content as I'll cover later.</p>
</li>
<li><p><strong>Client and sending</strong>: The ‘MailtrapClient’ is initialized with your Mailtrap API token, which authenticates the API request. The ‘send’ method is then called on the client, passing the ‘mail’ object.</p>
</li>
</ul>
<p>To create the client using the Mailtrap API token, take the following path within Mailtrap:<br><strong>Settings</strong> &gt; <strong>API Tokens</strong> &gt; <strong>Add Token</strong> </p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeLlNbf0Uiub9YYVxcfiNsZL6_uNHKfuO4dW6ZZGXWEGkF7X4mw82KMsrAWX4hA_u_jYqi1G8aoh1-vOnxKjdXKackVG8HdrsyfHulzaIJVMrMcxmZvllXcNOXVxG7hFOJXgl2VBw?key=CJmzmKUWxlFjIw3A041wXvaj" alt="Add API tokens" width="1600" height="560" loading="lazy"></p>
<p>With that, you can use the following command to send emails:</p>
<pre><code class="lang-python"><span class="hljs-comment"># create client and send</span>
client = mt.MailtrapClient(token=<span class="hljs-string">"your-api-key"</span>)
client.send(mail)
</code></pre>
<p>Finally, here’s the SDK script for sending a bare-bones ‘plaintext’ email via Python SDK.</p>
<pre><code class="lang-python"> <span class="hljs-keyword">from</span> mailtrap <span class="hljs-keyword">import</span> Mail, Address, MailtrapClient

<span class="hljs-comment"># Create a Mail object with basic details for a plain text email</span>
mail = Mail(
    <span class="hljs-comment"># Specify the sender's email address and optional name</span>
    sender=Address(email=<span class="hljs-string">"mailtrap@example.com"</span>, name=<span class="hljs-string">"Mailtrap Test"</span>),
    <span class="hljs-comment"># Specify one or more recipients; here we use a list with a single recipient</span>
    to=[Address(email=<span class="hljs-string">"your@email.com"</span>, name=<span class="hljs-string">"Your Name"</span>)],
    <span class="hljs-comment"># Subject of the email</span>
    subject=<span class="hljs-string">"Simple Plain Text Email"</span>,
    <span class="hljs-comment"># The plain text content of the email</span>
    text=<span class="hljs-string">"This is a plain text email sent using the Mailtrap SDK. Simple and straightforward."</span>,
    <span class="hljs-comment"># Optional: categorize this email for easier sorting or management in the Mailtrap service</span>
    category=<span class="hljs-string">"Test"</span>,
    <span class="hljs-comment"># Optional: Additional headers can be specified, but are not required for plain text emails</span>
    headers={<span class="hljs-string">"X-Example-Header"</span>: <span class="hljs-string">"HeaderValue"</span>}
)

<span class="hljs-comment"># Initialize the MailtrapClient with your API token</span>
client = MailtrapClient(token=<span class="hljs-string">"your-api-key"</span>)

<span class="hljs-comment"># Send the email using the client's send method</span>
client.send(mail)

print(<span class="hljs-string">"Plain text email sent successfully."</span>)
</code></pre>
<p><strong>In the script</strong>:</p>
<ul>
<li><p>The imported classes include ‘MailtrapClient’, ‘Mail’, and ‘Address’ because I’m sending a plain text message. </p>
</li>
<li><p>The ‘Mail’ object contains:</p>
<ul>
<li><p>‘Mail’ constructor to create the object.</p>
</li>
<li><p>‘Sender’ which uses ‘Address’ class to define the name and email of the sender.</p>
</li>
<li><p>‘to’ which is typically an ‘Address’ objects list, but since this is a plain text email, it usually has direct recipients instead of the list. </p>
</li>
<li><p>‘subject’ which is the subject of the email. </p>
</li>
<li><p>‘text’ which contains the email content (in ‘plaintext’)</p>
</li>
<li><p>‘headers’ and ‘category’ which are optional fields that help better manage your emails. </p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>The email sending flow:</p>
<ul>
<li><p>‘MailtrapClient’ gets created and authenticated via the API token. </p>
</li>
<li><p>The ‘MailtrapClient’ ‘send’ method gets called and passes the ‘mail’ object as an email-sending argument.  </p>
</li>
<li><p>The “Plain text email sent successfully.” message gets printed to confirm the action. </p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-refactor-the-script-to-include-html-and-attachments">Refactor the script to include HTML and attachments</h3>
<p>Again, it’s pretty straightforward to refactor the script using the ‘MIMEMultipart’ class for more complex email structures. </p>
<p>Here’s the refactored code:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> mailtrap <span class="hljs-keyword">as</span> mt
<span class="hljs-keyword">from</span> email.mime.multipart <span class="hljs-keyword">import</span> MIMEMultipart
<span class="hljs-keyword">from</span> email.mime.text <span class="hljs-keyword">import</span> MIMEText

<span class="hljs-comment"># Create a multipart email message</span>
message = MIMEMultipart()
message[<span class="hljs-string">"Subject"</span>] = <span class="hljs-string">"HTML Email"</span>

<span class="hljs-comment"># Plain text version (for email clients that don't support HTML)</span>
message.attach(MIMEText(<span class="hljs-string">"This is the plain text version."</span>, <span class="hljs-string">"plain"</span>))

<span class="hljs-comment"># HTML version</span>
html_content = <span class="hljs-string">"""\
&lt;html&gt;
  &lt;body&gt;
    &lt;h1&gt;Welcome to Mailtrap!&lt;/h1&gt;
    &lt;p&gt;This is an HTML email with some &lt;b&gt;bold text&lt;/b&gt; and a &lt;a href="https://example.com"&gt;link&lt;/a&gt;.&lt;/p&gt;
  &lt;/body&gt;
&lt;/html&gt;
"""</span>
message.attach(MIMEText(html_content, <span class="hljs-string">"html"</span>))

client = mt.MailtrapClient(token=<span class="hljs-string">"your-api-key"</span>)

<span class="hljs-comment"># Now send the email with Mailtrap's API</span>
mail = mt.Mail(
    sender=mt.Address(email=<span class="hljs-string">"mailtrap@example.com"</span>, name=<span class="hljs-string">"Mailtrap Test"</span>),
    to=[mt.Address(email=<span class="hljs-string">"your@email.com"</span>)],
    subject=<span class="hljs-string">"You are awesome!"</span>,
    html=message.as_string()  <span class="hljs-comment"># Pass the HTML content as a string</span>
)
client.send(mail)
</code></pre>
<h3 id="heading-environmental-setup-for-production">Environmental setup for production</h3>
<p>Before I dive into the details, I’d like to remind you of security best practices:</p>
<ol>
<li><p><strong>Securely store API keys and credentials</strong>: On production, never hardcode sensitive data like API keys, email login credentials, or other secrets directly into your source code. Doing so exposes your application.</p>
</li>
<li><p><strong>Use environment variables</strong>: By doing this, you can keep your credentials safe and easily switch between different configurations (like dev, staging, and production). </p>
</li>
</ol>
<p>Now, here’s how to set it all up:</p>
<ol>
<li><p>Use the ‘python-dotenv’ package to load environment variables from a ‘.env’ file. Install the lib with the following command:</p>
<pre><code class="lang-python"> pip install python-dotenv
</code></pre>
</li>
</ol>
<ol start="2">
<li><p>Create a ‘.env’ file in the root of your project to store your environment variables securely. This file will contain sensitive information, such as your Mailtrap API key, login credentials, and SMTP server details. Here’s an example:</p>
<pre><code class="lang-python"> SMTP_SERVER=smtp.mailtrap.io
 SMTP_PORT=<span class="hljs-number">587</span>
 SMTP_LOGIN=your_mailtrap_login
 SMTP_PASSWORD=your_mailtrap_password
 MAILTRAP_API_KEY=your_mailtrap_api_key
</code></pre>
</li>
</ol>
<p><strong>Important note</strong>: Ensure this ‘.env’ file is never pushed to version control (like Git). Add it to your ‘.gitignore’ to avoid accidental exposure.</p>
<ol start="3">
<li><p>Once you've created your ‘.env’ file, you need to load the variables into your Python script. At the top of your script, import the ‘dotenv’ package and call ‘load_dotenv()’ to load the environment variables.</p>
<pre><code class="lang-python"> <span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv
 <span class="hljs-keyword">import</span> os

 <span class="hljs-comment"># Load environment variables from the .env file</span>
 load_dotenv()

 <span class="hljs-comment"># Retrieve environment variables securely</span>
 smtp_server = os.getenv(<span class="hljs-string">"SMTP_SERVER"</span>)
 smtp_port = os.getenv(<span class="hljs-string">"SMTP_PORT"</span>)
 smtp_login = os.getenv(<span class="hljs-string">"SMTP_LOGIN"</span>)
 smtp_password = os.getenv(<span class="hljs-string">"SMTP_PASSWORD"</span>)
 mailtrap_api_key = os.getenv(<span class="hljs-string">"MAILTRAP_API_KEY"</span>)
</code></pre>
</li>
</ol>
<ol start="4">
<li><p>With the environment variables loaded, you can replace the hardcoded credentials in the script with these environment variables. Here’s an example:</p>
<pre><code class="lang-python"> <span class="hljs-keyword">import</span> smtplib
 <span class="hljs-keyword">from</span> email.mime.text <span class="hljs-keyword">import</span> MIMEText
 <span class="hljs-keyword">from</span> dotenv <span class="hljs-keyword">import</span> load_dotenv
 <span class="hljs-keyword">import</span> os

 <span class="hljs-comment"># Load environment variables</span>
 load_dotenv()

 <span class="hljs-comment"># Fetching SMTP credentials from environment variables</span>
 smtp_server = os.getenv(<span class="hljs-string">"SMTP_SERVER"</span>)
 smtp_port = os.getenv(<span class="hljs-string">"SMTP_PORT"</span>)
 smtp_login = os.getenv(<span class="hljs-string">"SMTP_LOGIN"</span>)
 smtp_password = os.getenv(<span class="hljs-string">"SMTP_PASSWORD"</span>)

 sender_email = <span class="hljs-string">"mailtrap@example.com"</span>
 receiver_email = <span class="hljs-string">"new@example.com"</span>
 subject = <span class="hljs-string">"Plain text email"</span>
 text = <span class="hljs-string">"""\
 Hi,
 Check out the new post on the Mailtrap blog:
 https://blog.mailtrap.io/2018/09/27/cloud-or-local-smtp-server/
 """</span>

 <span class="hljs-comment"># Create MIMEText object</span>
 message = MIMEText(text, <span class="hljs-string">"plain"</span>)
 message[<span class="hljs-string">"Subject"</span>] = subject
 message[<span class="hljs-string">"From"</span>] = sender_email
 message[<span class="hljs-string">"To"</span>] = receiver_email

 <span class="hljs-comment"># Send email using environment variables</span>
 <span class="hljs-keyword">with</span> smtplib.SMTP(smtp_server, smtp_port) <span class="hljs-keyword">as</span> server:
     server.starttls()  <span class="hljs-comment"># Secure the connection</span>
     server.login(smtp_login, smtp_password)
     server.sendmail(sender_email, receiver_email, message.as_string())

 print(<span class="hljs-string">"Email sent successfully!"</span>)
</code></pre>
</li>
</ol>
<h4 id="heading-pro-tips">Pro tips:</h4>
<p>First, ensure your environment variables are only accessible to authorized users. On a production server, this typically means only allowing access to the environment variables through the deployment configuration (for example, through Heroku’s config vars, AWS Secrets Manager, or other cloud-based secret management tools).</p>
<p>Second, use different environment variables for development, staging, and production. This ensures that your production environment is isolated and secured from the rest of your development process.</p>
<p>Once your environment variables are configured locally, deploy your application to a production environment. Make sure to set the same environment variables in your production server or service.</p>
<p>If you're deploying to platforms like Heroku, AWS, or Google Cloud, you can use their environment variable management tools to securely store and access your secrets without having to manage a ‘.env’ file manually.</p>
<h2 id="heading-wrapping-up">Wrapping up</h2>
<p>This quick tutorial provides more than enough to get started with sending emails in Python. And note that the scripts featured above can be extended to include HTML, multiple recipients, attachments, images, and so on. </p>
<p>If you’re interested in that and more security tips and best practices, you can check out the Mailtrap blog for more detailed tutorials.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Implement API Rate Limiting in Strapi CMS ]]>
                </title>
                <description>
                    <![CDATA[ Implementing rate limiting in web applications is a necessary web development best practice. In an article published earlier, I delved deep into the benefits and real life use cases of API rate limiting. Some of the benefits include its use by develo... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/implement-api-rate-limiting-in-strapi/</link>
                <guid isPermaLink="false">66e05529fcb93f325519038c</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwatobi ]]>
                </dc:creator>
                <pubDate>Tue, 10 Sep 2024 14:18:17 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1725233479497/7c12e6e4-a6d7-433a-b23b-f25c33037ffa.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Implementing rate limiting in web applications is a necessary web development best practice. In an <a target="_blank" href="https://www.freecodecamp.org/news/what-is-rate-limiting-web-apis/">article</a> published earlier, I delved deep into the benefits and real life use cases of API rate limiting.</p>
<p>Some of the benefits include its use by developers to restrict malicious access to websites, prevent DDoS attacks, conserve website resources, and ensure optimal web server performance.</p>
<p>This article covers the practical aspects of implementing rate limits in a Strapi application using several packages and techniques.</p>
<p>Let's get started.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-demo-project">Demo Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-koa2-rate-limit">Koa Rate Limiter</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-custom-strapi-api-rate-limiter">Custom Strapi Api Rate Limiter</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-express-rate-limiter-implementation">Express-rate-limiter Implementation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-demo-project">Demo Project</h2>
<p>We'll be building an e-commerce site using <a target="_blank" href="https://strapi.io/">Strapi</a> as our backend framework. We'll then set up a rate limiter in our Strapi application to help guarantee our backend security. Postman will serve as our tool for testing the API endpoints. Let's go on to create a default Strapi application.</p>
<p>To create a strapi application, enter <code>npx create-strapi-app@latest {project name}</code> on the command line and follow the commands provided. To make the installation more straightforward, stick with the <em>quick start</em> installation method and your app should be ready.</p>
<p>This installation modality automatically sets up an easy-to-use SQLite database. However, you could choose to use any other SQL database supported by Strapi.</p>
<p>Alternatively, you can download the starter repo for the project from <a target="_blank" href="https://github.com/oluwatobi2001/Strapi-default">here</a> and install the necessary dependencies via <code>npm install</code>. Thereafter, you can execute the Strapi application by navigating to the Strapi application code folder on the command line and run <code>npm run develop</code>.</p>
<p><img src="https://hackmd.io/_uploads/BkRn2PqrR.png" alt="Strapi Setup" width="600" height="400" loading="lazy"></p>
<p>On successful execution, you will be provided with the link to the localhost address to customize the application.</p>
<p><img src="https://hackmd.io/_uploads/SkkSavcS0.png" alt="Strapi launch" width="600" height="400" loading="lazy"></p>
<p>Navigating to the link will require you to create an admin login mail and password. Successful completion of this step will give you access to the backend dashboard.</p>
<p><img src="https://hackmd.io/_uploads/S1Vqxd5B0.png" alt="strapi login UI" width="600" height="400" loading="lazy"></p>
<p>You can utilize the Strapi dashboard UI to create APIs, or you can generate an API using <code>npm generate</code>. The APIs created will be used in completing the setup for the rate limiting functionality. We will be creating a product store for our e-commerce site. To easily set up products, kindly navigate to the Content-Type builder tab on the sidebar.  </p>
<p><img src="https://hackmd.io/_uploads/r1RzbO5BC.png" alt="strapi dashboard" width="600" height="400" loading="lazy"></p>
<p>The content-Type builder manager allows you to create various collections which will come in handy when setting up your APIs. In this case, the product and category collections will be created to enable you set up your product catalogues.</p>
<p><img src="https://hackmd.io/_uploads/B16rbu5rA.png" alt="Creating a category endpoint" width="600" height="400" loading="lazy"></p>
<p><img src="https://hackmd.io/_uploads/SJhdb_qSR.png" alt="Creating a product entry" width="600" height="400" loading="lazy"></p>
<p>After completing the creation of the collection types, you can easily add your products seamlessly into the backend database. In my case, I created phone brand products for sale.</p>
<p><img src="https://hackmd.io/_uploads/HyR9JT6fR.jpg" alt="Product creation demo" width="600" height="400" loading="lazy"></p>
<p>Also noteworthy is that the collections we created in the Strapi dashboard automatically creates an API folder for us within our codebase. We will then be working on the project codebase subsequently.</p>
<p>The next step in this tutorial is to set up an efficient rate limiter for our Strapi APIs created in the repo using the tools discussed above.</p>
<h2 id="heading-koa2-rate-limit">koa2-rate-limit</h2>
<p>In this section, we will be using the koa2-rate-limit package to build our project rate limiter. To install the package, navigate to your project folder on the command line and execute <code>npm i koa2-rate-limit</code>. On successful installation, navigate to the middleware subfolder within the API folder and create a code file. For ease of integration, name it as <strong>rateLimit.js</strong>.</p>
<p>After that, within the rate limit file, import and initialize the koa2-rate limit package.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> RateLimit = <span class="hljs-built_in">require</span>(<span class="hljs-string">"koa2-ratelimit"</span>).RateLimit;
</code></pre>
<p>Afterwards, we can configure the koa rate limiter to a specified time interval frame and the total number of requests.</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = <span class="hljs-function">(<span class="hljs-params">config, { strapi }</span>) =&gt;</span> {
  <span class="hljs-comment">// Configuring the rate limiter middleware</span>
  <span class="hljs-keyword">const</span> limiter = RateLimit.middleware({
    <span class="hljs-attr">interval</span>: { <span class="hljs-attr">min</span>: <span class="hljs-number">1</span> }, <span class="hljs-comment">// Time window in minutes</span>
    <span class="hljs-attr">max</span>: <span class="hljs-number">3</span>, <span class="hljs-comment">// Maximum number of requests per interval</span>
 });
</code></pre>
<p>In the code above, the rate limiter middleware was invoked and the time interval in which the rate limit gets applied was set to 1 minute. The maximum number of requests (max) was set to 3 for this tutorial. You can tweak this to suit your preference.</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">return</span> <span class="hljs-keyword">async</span> (ctx, next) =&gt; {


    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Apply the rate limiter to the current request</span>
      <span class="hljs-keyword">await</span> limiter(ctx, next);
 } <span class="hljs-keyword">catch</span> (err) {
      <span class="hljs-keyword">if</span> (err.status === <span class="hljs-number">429</span>) {
        <span class="hljs-comment">// Handle rate limit exceeded error</span>
        strapi.log.warn(<span class="hljs-string">'Rate limit exceeded.'</span>);
        ctx.status = <span class="hljs-number">429</span>;
        ctx.body = {
          <span class="hljs-attr">statusCode</span>: <span class="hljs-number">429</span>,
          <span class="hljs-attr">error</span>: <span class="hljs-string">'Too Many Requests'</span>,
          <span class="hljs-attr">message</span>: <span class="hljs-string">'You have exceeded the maximum number of requests. Please try again later.'</span>,
 };
 } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// Re-throw other errors to be handled by Strapi's error-handling middleware</span>
        <span class="hljs-keyword">throw</span> err;
 }
 }
</code></pre>
<p>The code above defines a middleware which gets executed whenever a function is made on any API. If the requests exceed the given maximum, an error code is outputted. Below is the full code.</p>
<pre><code class="lang-javascript"><span class="hljs-meta">
'use strict'</span>;

<span class="hljs-comment">/**
 * `RateLimit` middleware
 */</span>
<span class="hljs-keyword">const</span> RateLimit = <span class="hljs-built_in">require</span>(<span class="hljs-string">"koa2-ratelimit"</span>).RateLimit;

<span class="hljs-built_in">module</span>.exports = <span class="hljs-function">(<span class="hljs-params">config, { strapi }</span>) =&gt;</span> {
  <span class="hljs-comment">// Configuring the rate limiter middleware</span>
  <span class="hljs-keyword">const</span> limiter = RateLimit.middleware({
    <span class="hljs-attr">interval</span>: { <span class="hljs-attr">min</span>: <span class="hljs-number">1</span> }, <span class="hljs-comment">// Time window in minutes</span>
    <span class="hljs-attr">max</span>: <span class="hljs-number">3</span>, <span class="hljs-comment">// Maximum number of requests per interval</span>
 });

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">async</span> (ctx, next) =&gt; {

    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Apply the rate limiter to the current request</span>
      <span class="hljs-keyword">await</span> limiter(ctx, next);
 } <span class="hljs-keyword">catch</span> (err) {
      <span class="hljs-keyword">if</span> (err.status === <span class="hljs-number">429</span>) {
        <span class="hljs-comment">// Handle rate limit exceeded error</span>
        strapi.log.warn(<span class="hljs-string">'Rate limit exceeded.'</span>);
        ctx.status = <span class="hljs-number">429</span>;
        ctx.body = {
          <span class="hljs-attr">statusCode</span>: <span class="hljs-number">429</span>,
          <span class="hljs-attr">error</span>: <span class="hljs-string">'Too Many Requests'</span>,
          <span class="hljs-attr">message</span>: <span class="hljs-string">'You have exceeded the maximum number of requests. Please try again later.'</span>,
 };
 } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// Re-throw other errors to be handled by Strapi's error-handling middleware</span>
        <span class="hljs-keyword">throw</span> err;
 }
 }

 };
};
</code></pre>
<p>To ensure its seamless integration to all APIs within the Strapi project, the admin middlewares must also be configured.</p>
<pre><code class="lang-javascript">cconst rateLimit = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../middlewares/rateLimit'</span>);

<span class="hljs-built_in">module</span>.exports = [
 <span class="hljs-string">'strapi::logger'</span>,
 <span class="hljs-string">'strapi::errors'</span>,
 <span class="hljs-string">'strapi::security'</span>,
 <span class="hljs-string">'strapi::cors'</span>,
 <span class="hljs-string">'strapi::poweredBy'</span>,
 <span class="hljs-string">'strapi::query'</span>,
 <span class="hljs-string">'strapi::body'</span>,
 <span class="hljs-string">'strapi::session'</span>,
 <span class="hljs-string">'strapi::favicon'</span>,
 <span class="hljs-string">'strapi::public'</span>,

 {
   <span class="hljs-attr">name</span>: <span class="hljs-string">'global::rateLimit'</span>,
   <span class="hljs-attr">config</span>: {},
 },
];
</code></pre>
<p>With this, we have successfully configured the rate limiter powered by koa2-ratelimiter. Here are pictures of its execution.</p>
<p><img src="https://hackmd.io/_uploads/Bybbd-hj0.png" alt="Postman testing the categories endpoint" width="600" height="400" loading="lazy"></p>
<p><img src="https://hackmd.io/_uploads/r1Zb_-3jC.png" alt="rate limiting error response output" width="600" height="400" loading="lazy"></p>
<h2 id="heading-custom-strapi-api-rate-limiter">Custom Strapi Api Rate Limiter</h2>
<p>Within the <strong>rateLimit</strong> file in the <strong>API/middlewares</strong> folder, create a custom rate limiter by initializing a memory store.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> requestCounts = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
</code></pre>
<p>Thereafter, define your rate limit function and then configure the rate limiter.</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = <span class="hljs-function">(<span class="hljs-params">config, { strapi }</span>) =&gt;</span> {

  <span class="hljs-keyword">const</span> rateLimitConfig = strapi.config.get(<span class="hljs-string">'admin.rateLimit'</span>, {
    <span class="hljs-attr">interval</span>: <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>,  
    <span class="hljs-attr">max</span>: <span class="hljs-number">3</span>,  
 });
</code></pre>
<p>The time interval above is 1 minute while the maximum number of requests that can be made within the specified time interval is 3. You can tweak it to suit your preference.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">return</span> <span class="hljs-keyword">async</span> (ctx, next) =&gt; {

    <span class="hljs-keyword">const</span> ip = ctx.ip; 
    <span class="hljs-keyword">const</span> currentTime = <span class="hljs-built_in">Date</span>.now();

    <span class="hljs-keyword">if</span> (!requestCounts.has(ip)) {

      requestCounts.set(ip, { <span class="hljs-attr">count</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">startTime</span>: currentTime });
 } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">const</span> requestInfo = requestCounts.get(ip);


      <span class="hljs-keyword">if</span> (currentTime - requestInfo.startTime &gt; rateLimitConfig.interval) {
        requestInfo.count = <span class="hljs-number">1</span>;
        requestInfo.startTime = currentTime;
 } <span class="hljs-keyword">else</span> {

 }


      <span class="hljs-keyword">if</span> (requestInfo.count &gt; rateLimitConfig.max) {
        strapi.log.warn(<span class="hljs-string">`Rate limit exceeded for IP: <span class="hljs-subst">${ip}</span>`</span>);

        ctx.status = <span class="hljs-number">429</span>;
        ctx.body = {
          <span class="hljs-attr">statusCode</span>: <span class="hljs-number">429</span>,
          <span class="hljs-attr">error</span>: <span class="hljs-string">'Too Many Requests'</span>,
          <span class="hljs-attr">message</span>: <span class="hljs-string">'You have exceeded the maximum number of requests. Please try again later.'</span>,
 };
        <span class="hljs-keyword">return</span>;
 }
 }

    <span class="hljs-keyword">await</span> next();
 };
};
</code></pre>
<p>Afterwards, a middleware is defined which obtains the user IP address and then stores it in the memory store. The time interval is also set from the current time the request is made and the request count gets updated with every new request made.</p>
<p>If the requests made exceed the maximum expected requests within the time interval of 1 minute in our case, an error is thrown. Here is the full code below.</p>
<pre><code class="lang-javascript"><span class="hljs-meta">'use strict'</span>;
<span class="hljs-keyword">const</span> requestCounts = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();

<span class="hljs-built_in">module</span>.exports = <span class="hljs-function">(<span class="hljs-params">config, { strapi }</span>) =&gt;</span> {

  <span class="hljs-keyword">const</span> rateLimitConfig = strapi.config.get(<span class="hljs-string">'admin.rateLimit'</span>, {
    <span class="hljs-attr">interval</span>: <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>,  
    <span class="hljs-attr">max</span>: <span class="hljs-number">3</span>,  
 });

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">async</span> (ctx, next) =&gt; {

    <span class="hljs-keyword">const</span> ip = ctx.ip; 
    <span class="hljs-keyword">const</span> currentTime = <span class="hljs-built_in">Date</span>.now();

    <span class="hljs-keyword">if</span> (!requestCounts.has(ip)) {

      requestCounts.set(ip, { <span class="hljs-attr">count</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">startTime</span>: currentTime });
 } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">const</span> requestInfo = requestCounts.get(ip);


      <span class="hljs-keyword">if</span> (currentTime - requestInfo.startTime &gt; rateLimitConfig.interval) {
        requestInfo.count = <span class="hljs-number">1</span>;
        requestInfo.startTime = currentTime;
 } <span class="hljs-keyword">else</span> {

        requestInfo.count += <span class="hljs-number">1</span>;
 }


      <span class="hljs-keyword">if</span> (requestInfo.count &gt; rateLimitConfig.max) {


        ctx.status = <span class="hljs-number">429</span>;
        ctx.body = {
          <span class="hljs-attr">statusCode</span>: <span class="hljs-number">429</span>,
          <span class="hljs-attr">error</span>: <span class="hljs-string">'Too Many Requests'</span>,
          <span class="hljs-attr">message</span>: <span class="hljs-string">'You have exceeded the maximum number of requests. Please try again later.'</span>,
 };
        <span class="hljs-keyword">return</span>;
 }
 }

    <span class="hljs-keyword">await</span> next();
 };
};
</code></pre>
<p>Here is a demo of the project.</p>
<p><img src="https://hackmd.io/_uploads/BkIyHZ2j0.png" alt="fetching the categories on Postman" width="600" height="400" loading="lazy"></p>
<p><img src="https://hackmd.io/_uploads/HyxgHW2i0.png" alt="rate limiting error on Postman" width="600" height="400" loading="lazy"></p>
<h3 id="heading-express-rate-limiter-implementation">Express-rate-limiter Implementation</h3>
<p>Express rate limiter is also another important package that can be used to implement rate limiting in our project. Right now, this package will be used to implement a route-specific API rate limiting.</p>
<p>The next step in this tutorial is setting up an efficient rate limiter for our Strapi APIs created in the repo.</p>
<p>To set up rate limiters on our Strapi applications, we'll be working mainly on the <strong>routes</strong> file. This can be navigated to by accessing the <strong>src</strong> folder within the project root directory. Within the <strong>src</strong> folder, navigate to the <strong>API</strong> folder which contains all the API files for the collections created in the Strapi dashboard.</p>
<p><img src="https://hackmd.io/_uploads/S1ERbxndR.png" alt="the product route directory" width="600" height="400" loading="lazy"></p>
<p>The rate limiter will be enforced in the routes section of each API. For this tutorial, I will be using the products API as a demo API in this article.</p>
<pre><code class="lang-javascript"><span class="hljs-meta">'use strict'</span>;


<span class="hljs-comment">/**
 * product router
 */</span>

<span class="hljs-keyword">const</span> { createCoreRouter } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@strapi/strapi'</span>).factories;

<span class="hljs-built_in">module</span>.exports = createCoreRouter(<span class="hljs-string">'api::product.product'</span>);
</code></pre>
<p>This is the initial code setup in the <strong>routes.js</strong> file in our product API folder. The rate limiting tool of choice for this tutorial is express-rate-limit as it offers much simplicity and user-friendliness coupled with its efficiency. Here is a link to its <a target="_blank" href="https://www.npmjs.com/package/express-rate-limit">documentation</a>. To get this installed, navigate to the command line of the project directory and run</p>
<pre><code class="lang-bash">npm install express-rate-limit
</code></pre>
<p>On completion of its installation, we will be initializing it in the <strong>products</strong> file already created within the <strong>routes</strong> folder as follows.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { rateLimit } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express-rate-limit"</span>);
</code></pre>
<p>Go on and configure the rate limiter to your desired specifications.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> rateLimit = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express-rate-limit'</span>);

<span class="hljs-keyword">const</span> limiter = rateLimit({
  <span class="hljs-attr">windowMs</span>: <span class="hljs-number">3</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>, <span class="hljs-comment">// 3 minutes</span>
  <span class="hljs-attr">max</span>: <span class="hljs-number">2</span>, <span class="hljs-comment">// limit each IP to 2 requests per windowMs</span>
  <span class="hljs-attr">handler</span>: <span class="hljs-keyword">async</span> (req, res, next) =&gt; {
    <span class="hljs-keyword">const</span> ctx = strapi.requestContext.get();
    ctx.status = <span class="hljs-number">429</span>;
    ctx.body = {
      <span class="hljs-attr">message</span>: <span class="hljs-string">"Too many requests"</span>,
      <span class="hljs-attr">policy</span>: <span class="hljs-string">"rate limit"</span>
    };
    <span class="hljs-comment">// Ensure the response is ended after setting the response body and status</span>
    ctx.res.end();
  }
});

<span class="hljs-built_in">module</span>.exports = limiter;
</code></pre>
<p>The code above serves to configure the rate limiting parameters we intend to use for the file.</p>
<p><code>windowMs</code> represents the time interval in milliseconds for the number of requests. In our case, we specified a time of 3 minutes. Also, we specified the maximum number of requests that can be made within that same time frame. In our case, we used 2 for demo purposes.</p>
<p>However, the <code>limit</code> parameter also serves as an alternative to <code>max</code> parameter. Also included is the handler function that gets executed whenever the requests exceed the set number. It returns an <strong>Error 429</strong> with an error body containing “Too many requests”.</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> { createCoreRouter } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@strapi/strapi'</span>).factories;

<span class="hljs-built_in">module</span>.exports = createCoreRouter(<span class="hljs-string">'api::product.product'</span>, {
  <span class="hljs-attr">config</span>: {
    <span class="hljs-attr">find</span>: {
      <span class="hljs-attr">middlewares</span>: [
        <span class="hljs-keyword">async</span> (ctx, next) =&gt; {
          <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
            limiter(ctx.req, ctx.res, <span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
              <span class="hljs-keyword">if</span> (error) {
                ctx.status = <span class="hljs-number">429</span>;
                ctx.body = { <span class="hljs-attr">error</span>: error.message };
                reject(error);
              } <span class="hljs-keyword">else</span> {
                resolve();
              }
            });
          });
          <span class="hljs-keyword">await</span> next();
        }
      ]
    }
  }
});
</code></pre>
<p>The above code illustrates the use of the Strapi API middleware which serves to ensure that the rate limit is fulfilled before the onward execution of the API requests. It also ensures that the request is terminated when the rate limit gets exceeded. Here is the final code for the project.</p>
<pre><code class="lang-javascript"><span class="hljs-meta">'use strict'</span>;

<span class="hljs-comment">/**
 * product router
 */</span>

<span class="hljs-keyword">const</span> { createCoreRouter } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@strapi/strapi'</span>).factories;
<span class="hljs-keyword">const</span> rateLimit = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express-rate-limit'</span>);

<span class="hljs-keyword">const</span> limiter = rateLimit({
  <span class="hljs-attr">windowMs</span>: <span class="hljs-number">3</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>, <span class="hljs-comment">// 3 minutes</span>
  <span class="hljs-attr">max</span>: <span class="hljs-number">2</span>, <span class="hljs-comment">// limit each IP to 2 requests per windowMs</span>
  <span class="hljs-attr">handler</span>: <span class="hljs-keyword">async</span> (req, res, next) =&gt; {
    <span class="hljs-keyword">const</span> ctx = strapi.requestContext.get();
    ctx.status = <span class="hljs-number">429</span>;
    ctx.body = {
      <span class="hljs-attr">message</span>: <span class="hljs-string">'Too many requests'</span>,
      <span class="hljs-attr">policy</span>: <span class="hljs-string">'rate limit'</span>
    };
    <span class="hljs-comment">// Ensure the response is ended after setting the response body and status</span>
    ctx.res.end();
  }
});

<span class="hljs-built_in">module</span>.exports = createCoreRouter(<span class="hljs-string">'api::product.product'</span>, {
  <span class="hljs-attr">config</span>: {
    <span class="hljs-attr">find</span>: {
      <span class="hljs-attr">middlewares</span>: [
        <span class="hljs-keyword">async</span> (ctx, next) =&gt; {
          <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
            limiter(ctx.req, ctx.res, <span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {

              <span class="hljs-keyword">if</span> (error) {
                ctx.status = <span class="hljs-number">429</span>;
                ctx.body = { <span class="hljs-attr">error</span>: error.message };
                reject(error);
              } <span class="hljs-keyword">else</span> {
                resolve();
              }
            });
          });
          <span class="hljs-keyword">if</span> (ctx.status !== <span class="hljs-number">429</span>) {
            <span class="hljs-keyword">await</span> next();
          }
        }
      ]
    }
  }
});
</code></pre>
<p>Here is an image showing the rate limiting functionality.</p>
<p><img src="https://hackmd.io/_uploads/S116Wu9BR.png" alt="product endpoint testing in Postman" width="600" height="400" loading="lazy"></p>
<p><img src="https://hackmd.io/_uploads/S1zMGO5B0.png" alt="ratelimit successfully executed" width="600" height="400" loading="lazy"></p>
<p>You can also download the final code for the project <a target="_blank" href="https://github.com/oluwatobi2001/Strapi-project">here</a>. Having completed this, you can then go ahead to test the rate limiting functionality of your API. The Strapi application can be run by executing <code>npm run develop</code> in the command line.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>With this, we have come to the end of the tutorial. We hope you’ve learned essentially about rate limiting, its uses, tools and best practices.</p>
<p>You can also design multiple rate limiters within the code and implement them in any endpoint of your choice to test it out.</p>
<p>Feel free to drop any questions or comments. Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build CRUD Operations with .NET Core – A Todo API Handbook ]]>
                </title>
                <description>
                    <![CDATA[ Welcome to this comprehensive guide on building CRUD operations with .NET Core. We'll use a Todo API as our practical example so you can get hands-on experience as you learn.  Throughout this tutorial, you'll learn how to create, read, update, and de... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-crud-operations-with-dotnet-core-handbook/</link>
                <guid isPermaLink="false">66bb56fb3c5ab240beb8cd95</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ crud ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                    <category>
                        <![CDATA[ .net core ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Isaiah Clifford Opoku ]]>
                </dc:creator>
                <pubDate>Fri, 24 May 2024 14:33:16 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/05/Attractive.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Welcome to this comprehensive guide on building CRUD operations with .NET Core. We'll use a Todo API as our practical example so you can get hands-on experience as you learn. </p>
<p>Throughout this tutorial, you'll learn how to create, read, update, and delete Todo items, and how to leverage Entity Framework Core to interact with a database.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></li>
<li><a class="post-section-overview" href="#heading-how-to-enhance-your-development-experience-with-visual-studio-code-extensions">How to Enhance Your Development Experience with Visual Studio Code Extensions</a></li>
<li><a class="post-section-overview" href="#heading-learning-outcomes">Learning Outcomes</a></li>
<li><a class="post-section-overview" href="#heading-what-is-net-core">What is .NET Core?</a></li>
<li><a class="post-section-overview" href="#heading-net-core-vs-net-framework">.NET Core vs .NET Framework</a></li>
<li><a class="post-section-overview" href="#heading-step-1-set-up-your-project-directory">Step 1: Set Up Your Project Directory</a></li>
<li><a class="post-section-overview" href="#heading-step-2-establish-your-project-structure">Step 2: Establish Your Project Structure</a></li>
<li><a class="post-section-overview" href="#heading-step-3-create-the-todo-model">Step 3: Create the Todo Model</a></li>
<li><a class="post-section-overview" href="#heading-step-4-set-up-the-database-context">Step 4: Set Up the Database Context</a></li>
<li><a class="post-section-overview" href="#heading-step-5-define-data-transfer-objects-dtos">Step 5: Define Data Transfer Objects (DTOs)</a></li>
<li><a class="post-section-overview" href="#heading-step-6-implement-object-mapping-for-the-todo-api">Step 6: Implement Object Mapping for the Todo API</a></li>
<li><a class="post-section-overview" href="#heading-step-7-implement-global-exception-handling-middleware">Step 7: Implement Global Exception Handling Middleware</a>  </li>
<li><a class="post-section-overview" href="#heading-step-8-implement-the-service-layer-and-service-interface">Step 8: Implement the Service Layer and Service Interface</a></li>
<li><a class="post-section-overview" href="#heading-step-9-implement-the-createtodoasync-method-in-the-todoservices-class">step 9: Implement the CreateTodoAsync Method in the Service Class</a> </li>
<li><a class="post-section-overview" href="#heading-step-10-implement-the-getallasync-method-in-the-service-class">Step 10: Implement the GetAllAsync Method in the Service Class</a> </li>
<li><a class="post-section-overview" href="#heading-step-11-create-the-todocontroller-class">step 11: Create the TodoController Class  </a> </li>
<li><a class="post-section-overview" href="#step-12">Step 12: Implement the CreateTodoAsync  Method in the TodoController Class</a></li>
<li><a class="post-section-overview" href="#heading-step-13-implement-migrations-and-update-the-database">Step 13: Implement Migrations and Update the Database</a></li>
<li><a class="post-section-overview" href="#heading-step-14-verify-your-api-with-postman">Step 14: Verify Your API with Postman</a></li>
<li><a class="post-section-overview" href="#heading-step-15-retrieve-all-todo-items">Step 15: Retrieve All Todo Items</a> </li>
<li><a class="post-section-overview" href="#heading-step-16-implement-the-getbyidasync-method">Step 16: Implement the GetByIdAsync Method</a></li>
<li><a class="post-section-overview" href="#heading-step-17-implement-the-updatetodoasync-method">Step 17: Implement the UpdateTodoAsync Method</a></li>
<li><a class="post-section-overview" href="#heading-step-18-implement-the-deletetodoasync-method">Step 18: Implement the DeleteTodoAsync Method</a></li>
<li><a class="post-section-overview" href="#heading-step-19-test-your-api-endpoints-with-postman">Step 19: Test Your API Endpoints with Postman</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<p>Before we dive in, let's ensure you're equipped with the necessary prerequisites.</p>
<h2 id="prerequisites">Prerequisites</h2>

<p>Before you get started, make sure you have the necessary tools installed on your machine. Here are the download links:</p>
<ul>
<li><a target="_blank" href="https://dotnet.microsoft.com/download">.NET SDK</a></li>
<li><a target="_blank" href="https://code.visualstudio.com/download">Visual Studio Code</a></li>
<li><a target="_blank" href="https://visualstudio.microsoft.com/downloads/">Visual Studio 2019</a></li>
<li><a target="_blank" href="https://www.postman.com/downloads/">Postman</a></li>
<li><a target="_blank" href="https://www.microsoft.com/en-us/sql-server/sql-server-downloads">SQLServer</a></li>
</ul>
<p>After installing the .NET SDK, it's important to verify its installation and check the version. For this tutorial, we'll be using .NET 8.0.</p>
<p>To check the version of the .NET SDK installed on your machine, open the terminal and run the following command:</p>
<pre><code class="lang-bash">dotnet --version
</code></pre>
<p>If the .NET SDK is installed correctly, the version number will be displayed in the terminal:</p>
<pre><code class="lang-bash">8.0
</code></pre>
<p>If you see a different version number, ensure you have .NET 8.0 installed on your machine.</p>
<h2 id="enhancing-development"> How to Enhance Your Development Experience with Visual Studio Code Extensions</h2>

<p>Visual Studio Code, a lightweight and open-source code editor, is an excellent tool for building .NET Core applications. And you can further enhance its functionality with extensions that streamline the development process. </p>
<p>Here are two recommended extensions for .NET Core development:</p>
<ul>
<li><a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit">C# for Visual Studio Code</a></li>
<li><a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=adrianwilczynski.namespace">C# Namespace Autocompletion</a></li>
</ul>
<p>To install these extensions, follow these steps:</p>
<ol>
<li>Open Visual Studio Code.</li>
<li>Click on the Extensions icon in the Activity Bar on the side of the window to open the Extensions view.</li>
<li>In the search bar, type the name of the extension.</li>
<li>In the search results, locate the correct extension and click on the Install button.</li>
</ol>
<p>Here's how the Extensions view looks in Visual Studio Code:</p>
<ul>
<li><p>C# Devkit Extension for Visual Studio Code
<img src="https://www.freecodecamp.org/news/content/images/2024/05/DevKIt.png" alt="Extensions view for Devkit" width="600" height="400" loading="lazy"></p>
</li>
<li><p>Namespace Autocompletion Extension for Visual Studio Code
<img src="https://www.freecodecamp.org/news/content/images/2024/05/NameSpace.png" alt="Extensions view for Namespace Autocompletion" width="600" height="400" loading="lazy"></p>
</li>
</ul>
<p>In the images above, the extensions are already installed. If they're not installed on your system, you can do so by clicking on the Install button.</p>
<p>With these essential tools in place, we're now fully equipped to start building our Todo API.</p>
<h2 id="learning-outcomes"> Learning Outcomes </h2>


<p>By the end of this tutorial, you'll have learned how to:</p>
<ul>
<li>Set up a new .NET Core project using the .NET Core CLI</li>
<li>Define a model for a Todo item</li>
<li>Create a database context to interact with the database</li>
<li>Implement routing and controllers for the Todo API</li>
<li>Create a service class to handle business logic</li>
<li>Implement CRUD operations for the Todo API</li>
<li>Handle exceptions globally using middleware</li>
<li>Test the API endpoints using Postman</li>
</ul>
<p>If you're new to C# and .NET, don't worry. I'll explain all the concepts in depth to ensure you understand them. For additional information, you can refer to the <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/">C# documentation</a>.</p>
<p>Before we delve into the code, let's clarify what .NET Core is.</p>
<h2 id="what-is-net-core"> What is .NET Core? </h2>

<p>.NET Core, also known as ASP.NET, is a cross-platform framework that facilitates the building of web applications, APIs, and services. It's a free, open-source, and high-performance framework, designed for creating modern, cloud-based, internet-connected applications. It's the successor to the .NET Framework.</p>
<p>But what's the difference between .NET Core and .NET Framework?</p>
<h2 id="net-core-vs-net-framework"> .NET Core vs .NET Framework </h2>

<p>.NET Core and .NET Framework are two distinct frameworks used for application development. .NET Core is a cross-platform framework that operates on Windows, macOS, and Linux. It's a modular, open-source, and free-to-use framework, designed for building modern, cloud-based, internet-connected applications.</p>
<p>On the other hand, <code>.NET Framework</code> is a <code>Windows-only framework</code> used for building <code>Windows desktop</code> <code>applications</code>, <code>web applications</code>, and services. Unlike .NET Core, it's not open-source or free to use. However, it's a mature framework that has been around for a long time.</p>
<p>With a foundational understanding of .NET Core and .NET Framework under your belt, we're ready to dive into building our Todo API.</p>
<p>In this tutorial, we'll leverage .NET Core to construct a Todo API that performs CRUD operations. Our journey will take us through creating a new project, defining the Todo model, setting up the database, and implementing the CRUD operations.</p>
<p>Let's begin with Visual Studio Code. In this tutorial, we'll be using the .NET Core CLI to create our project and build our API. If you prefer Visual Studio 2019, you can follow along using that IDE as well but we will be using Visual Studio Code for this article. </p>
<h2 id="step-1">  Step 1: Set Up Your Project Directory </h2>

<p>First, navigate to the directory where you want to house your project. This could be any folder on your system where you'd like to store your code.</p>
<p>Once you're in the desired directory, open the terminal. You can do this in Visual Studio Code by going to <code>View -&gt; Terminal</code> or by pressing Ctrl + a  backtick.</p>
<p>With the terminal open, type the following command:</p>
<pre><code class="lang-bash">dotnet new webapi -n TodoAPI
</code></pre>
<p>This command instructs the .NET Core CLI to create a new web API project named <code>TodoAPI</code>. The <code>-n</code> option specifies the name of the project.</p>
<p> <img src="https://www.freecodecamp.org/news/content/images/2024/05/TerminalCreatingNewAPI.png" alt="Creating a new API with the .NET Core CLI" width="600" height="400" loading="lazy"></p>
<p>The image above illustrates how to execute the command in the terminal.</p>
<p>After pressing the 'Enter' key, the .NET Core CLI will start generating the necessary files for your project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/ProjectFile.png" alt=".NET project folder structure" width="600" height="400" loading="lazy"></p>
<p>The image above showcases the generated project structure. It includes all the necessary files and directories required for a .NET Core web API project.</p>
<p>With the project files and folders generated by the .NET Core CLI, let's take a moment to understand the purpose of each file.</p>
<ul>
<li><p><code>appsettings.json</code>: This file houses the application's configuration settings. It's the go-to place for storing connection strings, logging configurations, and other settings.</p>
</li>
<li><p><code>Program.cs</code>: Serving as the application's entry point, this file is responsible for setting up the host and configuring the services.</p>
</li>
<li><p><code>TodoAPI.csproj</code>: This project file contains metadata about your project, including references to the necessary packages and libraries.</p>
</li>
<li><p><code>appsettings.Development.json</code>: This file is designed for configuration settings specific to the development environment. It's ideal for storing environment-specific settings. But for the purpose of this tutorial, we'll be using the <code>appsettings.json</code> file instead.</p>
</li>
<li><p><code>TodoAPI.http</code>: This file is typically used to test API endpoints using the REST Client extension in Visual Studio Code, as it contains sample requests for the API endpoints. However, in this tutorial, we'll be using Postman for testing, so we won't need this file and will proceed to delete it.</p>
</li>
</ul>
<h2 id="step-2">  Step 2: Establish Your Project Structure  </h2>

<p>Having set up our project directory, it's time to lay out the structure of our project. We'll be creating several folders, each with a specific purpose:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/ProjectFolder.png" alt="project folder structure" width="600" height="400" loading="lazy"></p>
<ul>
<li><code>AppDataContext</code>: This folder will contain the database context, which is responsible for interacting with the database.</li>
<li><code>Contracts</code>: This folder will house our Data Transfer Objects (DTOs), which are used to shape the data sent between the client and the server.</li>
<li><code>Models</code>: This folder will contain the Todo model, which represents the structure of a Todo item.</li>
<li><code>Controllers</code>: This folder will house the TodoController, which handles incoming HTTP requests and sends responses.</li>
<li><code>Interfaces</code>: This folder will contain the IService interface, which defines the contract for our service class.</li>
<li><code>Services</code>: This folder will house the Service class, which implements the IService interface and contains the business logic of our application.</li>
<li><code>Mapping</code>: This folder will contain the mapping profile, which is used to map properties between different objects.</li>
<li><code>Middleware</code>: This folder will house the exception middleware, which handles exceptions globally across our application.</li>
</ul>
<p><em>Congratulations!</em> You've successfully set up your project directory and established the project structure. In the next section, we'll delve into defining the Todo model.</p>
<h3 id="heading-how-to-adjust-the-programcs-file-for-controllerbase">   How to Adjust the Program.cs File for ControllerBase </h3>

<p>When creating a new application using the <code>dotnet new webapi</code> command in .NET Core 6 and onwards, the generated project is a minimal web API project. But for this tutorial, we'll be using the traditional way of creating APIs, which requires some adjustments to the <code>Program.cs</code> file.</p>
<p>Before we dive into the changes, let's briefly discuss what a minimal API is.</p>
<h3 id="heading-understanding-minimal-apis">  Understanding Minimal APIs </h3>

<p>In .NET 6, Microsoft introduced a new feature known as Minimal APIs. These APIs are simpler and more lightweight than traditional APIs. They allow you to define your API routes and endpoints using a single file, without the need for controllers or startup classes. This approach facilitates the creation of small, focused APIs that are quick to build and easy to maintain.</p>
<p>However, for the purpose of this tutorial, we'll stick to the traditional API structure. Let's proceed with the necessary changes to the <code>Program.cs</code> file.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Program.cs.png" alt="Initial view of Program.cs" width="600" height="400" loading="lazy"></p>
<p>The image above displays the initial state of the <code>Program.cs</code> file when you create a new web API project. To adapt it for use with ControllerBase, we need to remove some code and add new code.</p>
<p>Start by deleting everything in the <code>Program.cs</code> file and replacing it with the following code:</p>
<pre><code class="lang-csharp">
 <span class="hljs-comment">// program.cs</span>
<span class="hljs-keyword">var</span> builder = WebApplication.CreateBuilder(args);

<span class="hljs-comment">// Add services to the container.</span>
builder.Services.AddControllers();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

<span class="hljs-keyword">var</span> app = builder.Build();

<span class="hljs-comment">// Configure the HTTP request pipeline.</span>
<span class="hljs-keyword">if</span> (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();
</code></pre>
<p>Now we can proceed to the next step, where we'll define the Todo model.</p>
<h2 id="step-3">  Step 3: Create the Todo Model  </h2>

<p>Before diving into creating our Todo model, it's important to know what a model does in <code>.NET CORE</code>. Think of a <code>model</code> as a <code>blueprint</code> for the kind of data our application will work with. It helps us organize and manage this data efficiently.</p>
<p>For our Todo list app, we need a clear picture of what each Todo item looks like. This means deciding on things like names, descriptions, whether it's done or not, deadlines, priorities, and when it was made or changed. By being clear about these details, we can handle and show our Todo items well.</p>
<h3 id="heading-meet-the-todo-model">  Meet the Todo Model </h3>

<p>Now, let's make our idea real by creating the <code>Todo</code> model. This model is like a template for our Todo items, making sure they have all the right pieces.</p>
<p>Let's create a new file called <code>Todo.cs</code> in the <code>Models</code> folder and fill it with this code:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Models/Todo.cs</span>
<span class="hljs-keyword">using</span> System.ComponentModel.DataAnnotations;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">TodoAPI.Models</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Todo</span>
    {
        [<span class="hljs-meta">Key</span>]
        <span class="hljs-keyword">public</span> Guid Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Title { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> IsComplete { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> DateTime DueDate { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Priority { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> DateTime CreatedAt { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> DateTime UpdatedAt { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Todo</span>(<span class="hljs-params"></span>)</span>
        {
            IsComplete = <span class="hljs-literal">false</span>;
        }
    }
}
</code></pre>
<p>Here's what each part of the <code>Todo</code> model means:</p>
<ul>
<li><strong>Id</strong>: A special number that makes each Todo item unique.</li>
<li><strong>Title</strong>: The name of the Todo item.</li>
<li><strong>Description</strong>: Extra details about the Todo item.</li>
<li><strong>IsComplete</strong>: Whether the Todo item is finished or not.</li>
<li><strong>DueDate</strong>: The date by which the Todo item needs to be done.</li>
<li><strong>Priority</strong>: How important the Todo item is.</li>
<li><strong>CreatedAt</strong> and <strong>UpdatedAt</strong>: When the Todo item was first made and last changed.</li>
</ul>
<p>The <code>[Key]</code> tag tells us that <code>Id</code> is the main way to identify each Todo item in our database.</p>
<p>By having a clear <code>Todo</code> model, we can easily keep track of and display our Todo items in the best way possible.</p>
<p>In ASP.NET Core, models can be used to represent a variety of things. One such use case is error handling. When an error occurs in our application, we can create a model for that error and return it to the client. </p>
<p>Let's create a model specifically for error handling in our application.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Models/ErrorResponse.cs</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">TodoAPI.Models</span>
{
       <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ErrorResponse</span>
 {
     <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Title { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
     <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> StatusCode { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
     <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Message { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
 }
}
</code></pre>
<p>This ErrorResponse model will be used to return error messages to the client when an error occurs in our application. It includes a title for the error, massage, and a status code, providing the client with useful information about what went wrong.</p>
<p>Let's define another model to manage our database connection string.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Models/DbSettings.cs </span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">TodoAPI.Models</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DbSettings</span>
    {
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> ConnectionString { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    }
}
</code></pre>
<p>The <code>DbSettings</code> model is designed to encapsulate the connection string for our database. It contains a single property, <code>ConnectionString</code>, which will store the actual connection string value.</p>
<p>With our <code>Todo</code> model in place, we're now ready to proceed with setting up the database context.</p>
<p>Before we begin setting up our database, we need to install the necessary packages for our project.</p>
<h3 id="heading-package-installation">   Package Installation </h3>


<p>To set up our project, we need to install several packages. We'll use the dotnet CLI for this task. </p>
<p>Before we begin, ensure you're in the root directory of your project. If you're unsure of your current location in the terminal, you can verify it by running the following command:</p>
<pre><code class="lang-bash">ls
</code></pre>
<p>This command will list all the files and folders in your current directory. The image below shows the terminal output after running the <code>ls</code> command.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/ls-terminal.png" alt="Terminal ls file" width="600" height="400" loading="lazy"></p>
<p>If your terminal output matches the image above, you're in the correct directory to install the packages.</p>
<p>Now, let's install the packages:</p>
<pre><code class="lang-bash">dotnet add package Microsoft.EntityFrameworkCore --version 8.0.0 
dotnet add package Microsoft.EntityFrameworkCore.Design --version 8.0.0
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 8.0.0
dotnet add package AutoMapper --version 13.0.1
</code></pre>
<p>Here's a brief overview of what these packages do:</p>
<ul>
<li><code>Microsoft.EntityFrameworkCore</code>: Provides the core Entity Framework Core functionality, enabling us to interact with our database.</li>
<li><code>Microsoft.EntityFrameworkCore.Design</code>: Includes design-time components for Entity Framework Core, such as migrations.</li>
<li><code>Microsoft.EntityFrameworkCore.SqlServer</code>: Allows us to use SQL Server as our database provider.</li>
<li><code>AutoMapper</code>: Simplifies object-to-object mapping, making it easier to map properties between different objects.</li>
</ul>
<p><strong>Note</strong>: Ensure you install the same versions of the packages as shown above to avoid any compatibility issues.</p>
<p>To confirm that all the packages have been installed successfully, navigate to the <code>TodoAPI.csproj</code> file located in the root directory of your project. The installed packages should be listed under the <code>ItemGroup</code> section.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">Sdk</span>=<span class="hljs-string">"Microsoft.NET.Sdk.Web"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">TargetFramework</span>&gt;</span>net8.0<span class="hljs-tag">&lt;/<span class="hljs-name">TargetFramework</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Nullable</span>&gt;</span>enable<span class="hljs-tag">&lt;/<span class="hljs-name">Nullable</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ImplicitUsings</span>&gt;</span>enable<span class="hljs-tag">&lt;/<span class="hljs-name">ImplicitUsings</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">InvariantGlobalization</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">InvariantGlobalization</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"AutoMapper"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"13.0.1"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.AspNetCore.OpenApi"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.EntityFrameworkCore"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.EntityFrameworkCore.Design"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.0"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">IncludeAssets</span>&gt;</span>runtime; build; native; contentfiles; analyzers; buildtransitive<span class="hljs-tag">&lt;/<span class="hljs-name">IncludeAssets</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">PrivateAssets</span>&gt;</span>all<span class="hljs-tag">&lt;/<span class="hljs-name">PrivateAssets</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">PackageReference</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.EntityFrameworkCore.SqlServer"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Swashbuckle.AspNetCore"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"6.4.0"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p>The above <code>TodoAPI.csproj</code> file shows the installed packages listed under the <code>ItemGroup</code> section. If your <code>TodoAPI.csproj</code> file reflects the same, it confirms that the packages have been installed successfully.</p>
<p>With the necessary packages installed, we're now ready to set up the database context for our Todo API.</p>
<h2 id="step-4">  Step 4: Set Up the Database Context  </h2>

<p>In ASP.NET Core, the database context is a crucial component that manages interactions with the database. It's responsible for tasks such as establishing a connection to the database, querying data, and saving changes. </p>
<p>To enable our <code>Todo API</code> to interact with the database, we need to create a database context.</p>
<p>Let's create a new file named <code>TodoDbContext</code> in the <code>AppDataContext</code> folder and populate it with the following code:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// AppDataContext/TodoDbContext.cs</span>

<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;
<span class="hljs-keyword">using</span> Microsoft.Extensions.Options;
<span class="hljs-keyword">using</span> TodoAPI.Models;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">TodoAPI.AppDataContext</span>
{

    <span class="hljs-comment">// TodoDbContext class inherits from DbContext</span>
     <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TodoDbContext</span> : <span class="hljs-title">DbContext</span>
     {

        <span class="hljs-comment">// DbSettings field to store the connection string</span>
         <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> DbSettings _dbsettings;

            <span class="hljs-comment">// Constructor to inject the DbSettings model</span>
         <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TodoDbContext</span>(<span class="hljs-params">IOptions&lt;DbSettings&gt; dbSettings</span>)</span>
         {
             _dbsettings = dbSettings.Value;
         }


        <span class="hljs-comment">// DbSet property to represent the Todo table</span>
         <span class="hljs-keyword">public</span> DbSet&lt;Todo&gt; Todos { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

         <span class="hljs-comment">// Configuring the database provider and connection string</span>

         <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnConfiguring</span>(<span class="hljs-params">DbContextOptionsBuilder optionsBuilder</span>)</span>
         {
             optionsBuilder.UseSqlServer(_dbsettings.ConnectionString);
         }

            <span class="hljs-comment">// Configuring the model for the Todo entity</span>
         <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnModelCreating</span>(<span class="hljs-params">ModelBuilder modelBuilder</span>)</span>
         {
             modelBuilder.Entity&lt;Todo&gt;()
                 .ToTable(<span class="hljs-string">"TodoAPI"</span>)
                 .HasKey(x =&gt; x.id);
         }
     }
}
</code></pre>
<p>Here's a breakdown of the <code>TodoDbContext</code> class:</p>
<ul>
<li><strong><code>TodoDbContext</code></strong>: This class, which inherits from <code>DbContext</code> (a part of Entity Framework Core), is the primary class that interacts with the database.</li>
<li><strong><code>_dbsettings</code></strong>: This private field stores the connection string for our database. We inject the <code>DbSettings</code> model, which we created earlier to manage the connection string, into the <code>TodoDbContext</code> class.</li>
<li><strong><code>Todos</code></strong>: This property represents the <code>Todo</code> table in our database. It's a <code>DbSet</code> of <code>Todo</code> objects, which allows us to query and save instances of <code>Todo</code>.</li>
<li><strong><code>OnConfiguring</code></strong>: This method configures the database provider and connection string. We're using SQL Server as our database provider, and the connection string is retrieved from the <code>DbSettings</code> model.</li>
<li><strong><code>OnModelCreating</code></strong>: This method configures the model for the <code>Todo</code> entity. We specify the table name, primary key, and other configurations for the <code>Todo</code> entity.</li>
</ul>
<p>To use our <code>TodoDbContext</code> for interacting with the database, we need to register it in the <code>Program.cs</code> file. This registration process is part of setting up the Dependency Injection (DI) container in .NET Core.</p>
<p>Here's how to do it:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Program.cs</span>


<span class="hljs-keyword">using</span> TodoAPI.AppDataContext;
<span class="hljs-keyword">using</span> TodoAPI.Models;

<span class="hljs-keyword">var</span> builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();



 <span class="hljs-comment">// Add  This to in the Program.cs file</span>
builder.Services.Configure&lt;DbSettings&gt;(builder.Configuration.GetSection(<span class="hljs-string">"DbSettings"</span>)); <span class="hljs-comment">// Add this line</span>
builder.Services.AddSingleton&lt;TodoDbContext&gt;(); <span class="hljs-comment">// Add this line</span>




<span class="hljs-keyword">var</span> app = builder.Build();

<span class="hljs-comment">// Add this line</span>

{
    <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> scope = app.Services.CreateScope(); <span class="hljs-comment">// Add this line</span>
    <span class="hljs-keyword">var</span> context = scope.ServiceProvider; <span class="hljs-comment">// Add this line</span>
}


<span class="hljs-keyword">if</span> (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseExceptionHandler();
app.UseAuthorization();

app.MapControllers();

app.Run();
</code></pre>
<p>In the code snippet above, we're doing two things:</p>
<ul>
<li>Configuring the database settings by binding the <code>DbSettings</code> section from the <code>appsettings.json</code> file to the <code>DbSettings</code> class. This allows us to access the database connection string in our application.</li>
<li>Registering the <code>TodoDbContext</code> with the DI container as a singleton service. This means that a single instance of <code>TodoDbContext</code> will be created and shared across the entire application.</li>
</ul>
<p>With the database context registered, we can now use it to perform CRUD operations on our Todo items.</p>
<p>Now let's check if everything is working fine by running the application.</p>
<pre><code class="lang-bash">
dotnet run
</code></pre>
<p>If you see the following output, it means your application is running successfully:</p>
<pre><code class="lang-bash">
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5086
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: E:\Todo\TodoAPI
</code></pre>
<p><strong>Note</strong>: If you encounter any errors, just make sure you've followed all the steps correctly and that the necessary packages have been installed successfully. If you see some warnings, you can ignore them for now.</p>
<p>With the <code>TodoDbContext</code> class now set up, we're ready to define the Contracts  for our application.</p>
<h2 id="step-5">  Step 5: Define Data Transfer Objects (DTOs) </h2>

<p>In the context of .NET development, a Data Transfer Object (DTO) is a simple object that carries data between processes. It's often used in conjunction with a service layer to shape the data sent between the client and the server. </p>
<p>For our Todo API, we'll define two DTOs: <code>CreateTodoRequest</code> and <code>UpdateTodoRequest</code>. These DTOs will help us enforce the structure and validation of the data sent to our API.</p>
<p>Navigate to the <code>Contracts</code> folder and create two new files: <code>CreateTodoRequest.cs</code> and <code>UpdateTodoRequest.cs</code>.</p>
<h3 id="heading-the-createtodorequest-file"> The <code>CreateTodoRequest</code> File </h3>


<p>The <code>CreateTodoRequest</code> DTO will define the structure and validation rules for creating a new Todo item. Add the following code to the <code>CreateTodoRequest.cs</code> file:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CreateTodoRequest</span>
{
    [<span class="hljs-meta">Required</span>]
    [<span class="hljs-meta">StringLength(100)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Title { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">StringLength(500)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">Required</span>]
    <span class="hljs-keyword">public</span> DateTime DueDate { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">Range(1, 5)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Priority { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre>
<p>In this DTO, we've defined properties for <code>Title</code>, <code>Description</code>, <code>DueDate</code>, and <code>Priority</code>. We've also added validation attributes like <code>[Required]</code>, <code>[StringLength]</code>, and <code>[Range]</code> to enforce certain rules on these properties.</p>
<h3 id="heading-the-updatetodorequest-file"> The <code>UpdateTodoRequest</code> File </h3>


<p>The <code>UpdateTodoRequest</code> DTO will define the structure and validation rules for updating an existing Todo item. Add the following code to the <code>UpdateTodoRequest.cs</code> file:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UpdateTodoRequest</span>
{
    [<span class="hljs-meta">StringLength(100)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Title { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">StringLength(500)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span>? IsComplete { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-keyword">public</span> DateTime? DueDate { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">Range(1, 5)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span>? Priority { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">UpdateTodoRequest</span>(<span class="hljs-params"></span>)</span>
    {
        IsComplete = <span class="hljs-literal">false</span>;
    }
}
</code></pre>
<p>In this DTO, we've defined properties for <code>Title</code>, <code>Description</code>, <code>IsComplete</code>, <code>DueDate</code>, and <code>Priority</code>. The <code>IsComplete</code> property is nullable, which means it can be set to <code>null</code> if not provided. We've also added validation attributes like <code>[StringLength]</code> and <code>[Range]</code> to enforce certain rules on these properties.</p>
<p>With these DTOs in place, we're now ready to implement the service layer for our Todo API.</p>
<p>Now test the application, and see if there are any errors.</p>
<pre><code class="lang-bash">
 dotnet  build
</code></pre>
<p>If you see the following output, it means your application is running successfully:</p>
<pre><code class="lang-bash">MSBuild version 17.8.3+195e7f5a3 <span class="hljs-keyword">for</span> .NET
  Determining projects to restore...
  All projects are up-to-date <span class="hljs-keyword">for</span> restore.
  TodoAPI -&gt; E:\Todo\TodoAPI\bin\Debug\net8.0\TodoAPI.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.94
</code></pre>
<p><strong>Note</strong>: If you encounter any errors, make sure you've followed all the steps correctly and that the necessary packages have been installed successfully. If you see some warnings, you can ignore them for now.</p>
<p>With the DTOs defined, we're now ready to implement the Mapping for the   Todo API.</p>
<h2 id="step-6">  Step 6: Implement Object Mapping for the Todo API </h2>

<p>Having defined the DTOs for our Todo API, the next step is to implement object mapping. This process allows us to convert between the DTOs and the Todo model, a critical aspect of data transformation in our application.</p>
<p>To streamline this process, we'll use the <code>AutoMapper</code> library. AutoMapper is a widely-used library that simplifies object-to-object mapping, making it easier to map properties between different objects.</p>
<p>We've already installed the <code>AutoMapper</code> package in our project. Now, in the <code>MappingProfiles</code> folder, create a new file named <code>AutoMapperProfile.cs</code> and add the following code:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> AutoMapper;
<span class="hljs-keyword">using</span> TodoAPI.Contracts;
<span class="hljs-keyword">using</span> TodoAPI.Models;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">TodoAPI.MappingProfiles</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AutoMapperProfile</span> : <span class="hljs-title">Profile</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">AutoMapperProfile</span>(<span class="hljs-params"></span>)</span>
        {
            CreateMap&lt;CreateTodoRequest, Todo&gt;()
                .ForMember(dest =&gt; dest.id, opt =&gt; opt.Ignore())
                .ForMember(dest =&gt; dest.CreatedAt, opt =&gt; opt.Ignore())
                .ForMember(dest =&gt; dest.UpdatedAt, opt =&gt; opt.Ignore());

            CreateMap&lt;UpdateTodoRequest, Todo&gt;()
                .ForMember(dest =&gt; dest.id, opt =&gt; opt.Ignore())
                .ForMember(dest =&gt; dest.CreatedAt, opt =&gt; opt.Ignore())
                .ForMember(dest =&gt; dest.UpdatedAt, opt =&gt; opt.Ignore());
        }
    }
}
</code></pre>
<p>Let's break down the <code>AutoMapperProfile</code> class:</p>
<ul>
<li><strong>AutoMapperProfile</strong>: This class, which inherits from <code>Profile</code> (a class provided by AutoMapper), allows us to define mapping configurations.</li>
<li><strong>CreateMap</strong>: This method creates a mapping between two objects. Here, we're mapping from <code>CreateTodoRequest</code> to <code>Todo</code> and from <code>UpdateTodoRequest</code> to <code>Todo</code>.</li>
<li><strong>ForMember</strong>: This method configures the mapping for a specific property. We're using it to ignore the <code>id</code>, <code>CreatedAt</code>, and <code>UpdatedAt</code> properties when mapping from the DTOs to the <code>Todo</code> model.</li>
</ul>
<p>Now let's add the automapper to the DI container in the <code>Program.cs</code> file.</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Program.cs</span>

<span class="hljs-keyword">using</span> TodoAPI.AppDataContext;
<span class="hljs-keyword">using</span> TodoAPI.Models;

<span class="hljs-keyword">var</span> builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();



 <span class="hljs-comment">// Add  This to in the Program.cs file</span>
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());  <span class="hljs-comment">// Add this line</span>


<span class="hljs-comment">// .....</span>

<span class="hljs-keyword">var</span> app = builder.Build();



<span class="hljs-comment">// .....</span>
<span class="hljs-keyword">if</span> (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseExceptionHandler();
app.UseAuthorization();

app.MapControllers();

app.Run();
</code></pre>
<p>With the mapping profiles in place, we can now implement the service layer for our Todo API.</p>
<h2 id="step-7"> Step 7: Implement Global Exception Handling Middleware </h2>

<p>As we progress with our Todo API, it's crucial to implement a mechanism for handling exceptions globally. This ensures that any exceptions that occur during the execution of our application are caught and handled appropriately, providing meaningful error messages to the client.</p>
<p>.NET 8 introduces the <code>IExceptionHandler</code> interface, which simplifies the process of creating a custom exception handler. This handler will catch all exceptions that occur in our application and return a consistent error response to the client.</p>
<p>Let's create a global exception handler in the <code>Middleware</code> folder. Create a new file named <code>GlobalExceptionHandler.cs</code> and add the following code:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Middleware/GlobalExceptionHandler.cs</span>

<span class="hljs-keyword">using</span> System.Net;
<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Diagnostics;
<span class="hljs-keyword">using</span> TodoAPI.Models;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">TodoAPI.Middleware</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">GlobalExceptionHandler</span> : <span class="hljs-title">IExceptionHandler</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ILogger&lt;GlobalExceptionHandler&gt; _logger;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">GlobalExceptionHandler</span>(<span class="hljs-params">ILogger&lt;GlobalExceptionHandler&gt; logger</span>)</span>
        {
            _logger = logger;
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> ValueTask&lt;<span class="hljs-keyword">bool</span>&gt; <span class="hljs-title">TryHandleAsync</span>(<span class="hljs-params">
            HttpContext httpContext,
            Exception exception,
            CancellationToken cancellationToken</span>)</span>
        {
            _logger.LogError(
                <span class="hljs-string">$"An error occurred while processing your request: <span class="hljs-subst">{exception.Message}</span>"</span>);

            <span class="hljs-keyword">var</span> errorResponse = <span class="hljs-keyword">new</span> ErrorResponse
            {
                Message = exception.Message
            };

            <span class="hljs-keyword">switch</span> (exception)
            {
                <span class="hljs-keyword">case</span> BadHttpRequestException:
                    errorResponse.StatusCode = (<span class="hljs-keyword">int</span>)HttpStatusCode.BadRequest;
                    errorResponse.Title = exception.GetType().Name;
                    <span class="hljs-keyword">break</span>;

                <span class="hljs-keyword">default</span>:
                    errorResponse.StatusCode = (<span class="hljs-keyword">int</span>)HttpStatusCode.InternalServerError;
                    errorResponse.Title = <span class="hljs-string">"Internal Server Error"</span>;
                    <span class="hljs-keyword">break</span>;
            }

            httpContext.Response.StatusCode = errorResponse.StatusCode;

            <span class="hljs-keyword">await</span> httpContext
                .Response
                .WriteAsJsonAsync(errorResponse, cancellationToken);

            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
        }
    }
}
</code></pre>
<p>Here's a breakdown of the <code>GlobalExceptionHandler</code> class:</p>
<ul>
<li><strong>GlobalExceptionHandler</strong>: This class implements the <code>IExceptionHandler</code> interface, enabling global exception handling in our application.</li>
<li><strong>TryHandleAsync</strong>: This method is invoked when an exception occurs. It logs the error message, creates an <code>ErrorResponse</code> object, sets the status code and title based on the exception type, and returns a consistent error response to the client.</li>
<li><strong>ErrorResponse</strong>: This class represents the error response returned to the client when an exception occurs. It contains properties for the error message, status code, and title.</li>
<li><strong>BadHttpRequestException</strong>: This case handles exceptions of type <code>BadHttpRequestException</code> and sets the status code and title accordingly.</li>
</ul>
<p>After setting up the global exception handler, we need to register it in our <code>Program.cs</code> file:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Program.cs</span>

<span class="hljs-keyword">using</span> TodoAPI.AppDataContext;
<span class="hljs-keyword">using</span> TodoAPI.Interface;
<span class="hljs-keyword">using</span> TodoAPI.Middleware;
<span class="hljs-keyword">using</span> TodoAPI.Models;
<span class="hljs-keyword">using</span> TodoAPI.Services;

<span class="hljs-keyword">var</span> builder = WebApplication.CreateBuilder(args);



builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();



<span class="hljs-comment">// ....</span>



builder.Services.AddExceptionHandler&lt;GlobalExceptionHandler&gt;(); <span class="hljs-comment">// Add this line</span>

builder.Services.AddProblemDetails();  <span class="hljs-comment">// Add this line</span>

<span class="hljs-comment">// Adding of login </span>
builder.Services.AddLogging();  <span class="hljs-comment">//  Add this line</span>



<span class="hljs-keyword">var</span> app = builder.Build();


<span class="hljs-comment">// ......</span>


<span class="hljs-keyword">if</span> (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection(); <span class="hljs-comment">// Add this line</span>

app.UseExceptionHandler();
app.UseAuthorization();

app.MapControllers();

app.Run();


<span class="hljs-comment">// ...</span>
</code></pre>
<h2 id="step-8"> Step 8: Implement the Service Layer and Service Interface </h2>

<p>In .NET development, the service layer encapsulates the core business logic of an application. It serves as a bridge between the controller and the database, ensuring a clean separation of concerns.</p>
<p>First, let's define an interface for our service layer.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Interfaces/ITodoServices.cs </span>

<span class="hljs-keyword">using</span> TodoAPI.Contracts;
<span class="hljs-keyword">using</span> TodoAPI.Models;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">TodoAPI.Interface</span>
{
     <span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">ITodoServices</span>
     {
         Task&lt;IEnumerable&lt;Todo&gt;&gt; GetAllAsync();
         <span class="hljs-function">Task&lt;Todo&gt; <span class="hljs-title">GetByIdAsync</span>(<span class="hljs-params">Guid id</span>)</span>;
         <span class="hljs-function">Task <span class="hljs-title">CreateTodoAsync</span>(<span class="hljs-params">CreateTodoRequest request</span>)</span>;
         <span class="hljs-function">Task <span class="hljs-title">UpdateTodoAsync</span>(<span class="hljs-params">Guid id, UpdateTodoRequest request</span>)</span>;
         <span class="hljs-function">Task <span class="hljs-title">DeleteTodoAsync</span>(<span class="hljs-params">Guid id</span>)</span>;
     }
}
</code></pre>
<p>Here's a brief overview of the methods defined in the <code>ITodoServices</code> interface:</p>
<ul>
<li><code>GetAllAsync</code>: Retrieves all Todo items from the database.</li>
<li><code>GetByIdAsync</code>: Fetches a specific Todo item by its <code>Id</code>.</li>
<li><code>CreateTodoAsync</code>: Adds a new Todo item to the database.</li>
<li><code>UpdateTodoAsync</code>: Modifies an existing Todo item in the database.</li>
<li><code>DeleteTodoAsync</code>: Removes a Todo item from the database.</li>
</ul>
<p>Now, let's create a service class that implements these methods. We'll use Dependency Injection to inject the <code>ITodoServices</code> interface into the service class, making our code more modular, testable, and maintainable.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Services/TodoServices.cs</span>

<span class="hljs-keyword">using</span> TodoAPI.Interface;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">TodoAPI.Services</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TodoServices</span> : <span class="hljs-title">ITodoServices</span>
    {

    }
}
</code></pre>
<p>At this point, you'll encounter an error because we haven't implemented the methods from the <code>ITodoServices</code> interface in the <code>TodoServices</code> class. </p>
<p>The below image shows the error message that appears when the methods from the <code>ITodoServices</code> interface are not implemented in the <code>TodoServices</code> class.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/InterfaceError.png" alt="Error in the TodoServices class" width="600" height="400" loading="lazy"></p>
<p>To resolve this, hover over <code>ITodoServices</code>, click on the light bulb icon that appears, and select 'Implement interface'. This will automatically generate stubs for the methods defined in the <code>ITodoServices</code> interface.</p>
<p>The below image shows the 'Implement interface' option that appears when hovering over <code>ITodoServices</code> in the <code>TodoServices</code> class.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/QickFixt.png" alt="Implementing the ITodoServices interface" width="600" height="400" loading="lazy"></p>
<p>After implementing the interface, the <code>TodoServices</code> class should look like this:</p>
<pre><code class="lang-csharp">

<span class="hljs-comment">// Services/TodoServices.cs</span>
<span class="hljs-keyword">using</span> TodoAPI.Contracts;
<span class="hljs-keyword">using</span> TodoAPI.Interface;
<span class="hljs-keyword">using</span> TodoAPI.Models;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">TodoAPI.Services</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TodoServices</span> : <span class="hljs-title">ITodoServices</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">CreateTodoAsync</span>(<span class="hljs-params">CreateTodoRequest request</span>)</span>
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">DeleteTodoAsync</span>(<span class="hljs-params">Guid id</span>)</span>
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
        }

        <span class="hljs-keyword">public</span> Task&lt;IEnumerable&lt;Todo&gt;&gt; GetAllAsync()
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;Todo&gt; <span class="hljs-title">GetByIdAsync</span>(<span class="hljs-params">Guid id</span>)</span>
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">UpdateTodoAsync</span>(<span class="hljs-params">Guid id, UpdateTodoRequest request</span>)</span>
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
        }
    }
}
</code></pre>
<h3 id="heading-how-to-enhance-the-todoservices-class-with-dependency-injection"> How to Enhance the TodoServices Class with Dependency Injection </h3>

<p>Now, let's enrich our <code>TodoServices</code> class with some essential properties. These properties will provide the necessary tools for interacting with the database, logging, and object mapping.</p>
<p>At the top of the <code>TodoServices</code> class, add the following properties:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Services/TodoServices.cs</span>

<span class="hljs-comment">// ...</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> TodoDbContext _context;
<span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ILogger&lt;TodoServices&gt; _logger;
<span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IMapper _mapper;

<span class="hljs-comment">// ...</span>
</code></pre>
<p>Here's a brief explanation of these properties:</p>
<ul>
<li><code>_context</code>: An instance of the <code>TodoDbContext</code> class, enabling us to interact with the database.</li>
<li><code>_logger</code>: An instance of the <code>ILogger</code> class, facilitating logging throughout our application.</li>
<li><code>_mapper</code>: An instance of the <code>IMapper</code> class, allowing us to perform object-to-object mapping using AutoMapper.</li>
</ul>
<p>Next, we'll update the constructor of the <code>TodoServices</code> class to inject these dependencies:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Services/TodoServices.cs</span>

<span class="hljs-comment">// ...</span>

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TodoServices</span>(<span class="hljs-params">TodoDbContext context, ILogger&lt;TodoServices&gt; logger, IMapper mapper</span>)</span>
{
    _context = context;
    _logger = logger;
    _mapper = mapper;
}

<span class="hljs-comment">// ...</span>
</code></pre>
<p>With these dependencies injected, we're now ready to implement the methods defined in the <code>ITodoServices</code> interface. We'll begin with the <code>GetAllAsync</code> method in the next section.</p>
<h2 id="step-9"> Step 9: Implement the CreateTodoAsync Method in the TodoServices Class </h2>

<p>Now, let's implement the <code>CreateTodoAsync</code> method in the <code>TodoServices</code> class. This method will handle the creation of new Todo items in our database.</p>
<p>Navigate to the <code>TodoServices</code> class and add the following code to the <code>CreateTodoAsync</code> method:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Services/TodoServices.cs</span>

<span class="hljs-comment">// ...</span>

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">CreateTodoAsync</span>(<span class="hljs-params">CreateTodoRequest request</span>)</span>
{
    <span class="hljs-keyword">try</span>
    {
        <span class="hljs-keyword">var</span> todo = _mapper.Map&lt;Todo&gt;(request);
        todo.CreatedAt = DateTime.UtcNow;
        _context.Todos.Add(todo);
        <span class="hljs-keyword">await</span> _context.SaveChangesAsync();
    }
    <span class="hljs-keyword">catch</span> (Exception ex)
    {
        _logger.LogError(ex, <span class="hljs-string">"An error occurred while creating the Todo item."</span>);
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Exception(<span class="hljs-string">"An error occurred while creating the Todo item."</span>);
    }
}

<span class="hljs-comment">// ...</span>
</code></pre>
<p>Here's a breakdown of the <code>CreateTodoAsync</code> method:</p>
<ul>
<li><strong>Mapping</strong>: We use AutoMapper to convert the <code>CreateTodoRequest</code> object into a <code>Todo</code> entity.</li>
<li><strong>CreatedAt</strong>: We set the <code>CreatedAt</code> property of the <code>Todo</code> entity to the current UTC date and time.</li>
<li><strong>Adding to the Database</strong>: We add the <code>Todo</code> entity to the <code>Todos</code> DbSet in our context and save the changes asynchronously.</li>
<li><strong>Error Handling</strong>: We catch any exceptions that might occur during the process, log the error, and throw a new exception with a descriptive error message.</li>
</ul>
<p>With the <code>CreateTodoAsync</code> method implemented, we can now create new Todo items in our database.</p>
<h2 id="step-10"> Step 10: Implement the GetAllAsync Method in the Service Class </h2>

<p>Next, let's implement the <code>GetAllAsync</code> method in the <code>TodoServices</code> class. This method will retrieve all Todo items from the database.</p>
<p>Navigate to the <code>TodoServices</code> class and add the following code to the <code>GetAllAsync</code> method:</p>
<pre><code class="lang-csharp">

<span class="hljs-comment">// Services/TodoServices.cs</span>

<span class="hljs-comment">// ...</span>


 <span class="hljs-comment">// Get all TODO Items from the database </span>
 <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IEnumerable&lt;Todo&gt;&gt; GetAllAsync()
 {
     <span class="hljs-keyword">var</span> todo= <span class="hljs-keyword">await</span> _context.Todos.ToListAsync();
     <span class="hljs-keyword">if</span> (todo == <span class="hljs-literal">null</span>)
     {
         <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Exception(<span class="hljs-string">" No Todo items found"</span>);
     }
     <span class="hljs-keyword">return</span> todo;

 }

<span class="hljs-comment">// ...</span>
</code></pre>
<p>Here's a breakdown of the <code>GetAllAsync</code> method:</p>
<ul>
<li><p><strong>Retrieving Todo Items</strong>: We use Entity Framework Core's <code>ToListAsync</code> method to fetch all Todo items from the database.</p>
</li>
<li><p><strong>Error Handling</strong>: If no Todo items are found, we throw an exception with a descriptive error message.</p>
</li>
</ul>
<p>Now Your Service class should look like this:</p>
<pre><code class="lang-csharp">


<span class="hljs-keyword">using</span> AutoMapper;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;
<span class="hljs-keyword">using</span> TodoAPI.AppDataContext;
<span class="hljs-keyword">using</span> TodoAPI.Contracts;
<span class="hljs-keyword">using</span> TodoAPI.Interface;
<span class="hljs-keyword">using</span> TodoAPI.Models;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">TodoAPI.Services</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TodoServices</span> : <span class="hljs-title">ITodoServices</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> TodoDbContext _context;
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ILogger&lt;TodoServices&gt; _logger;
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IMapper _mapper;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TodoServices</span>(<span class="hljs-params">TodoDbContext context, ILogger&lt;TodoServices&gt; logger, IMapper mapper</span>)</span>
        {
            _context = context;
            _logger = logger;
            _mapper = mapper;
        }




        <span class="hljs-comment">//  Create Todo for it be save in the datbase </span>

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">CreateTodoAsync</span>(<span class="hljs-params">CreateTodoRequest request</span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-keyword">var</span> todo = _mapper.Map&lt;Todo&gt;(request);
                todo.CreatedAt = DateTime.Now;
                _context.Todos.Add(todo);
                <span class="hljs-keyword">await</span> _context.SaveChangesAsync();
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                _logger.LogError(ex, <span class="hljs-string">"An error occurred while creating the todo item."</span>);
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Exception(<span class="hljs-string">"An error occurred while creating the todo item."</span>);
            }
        }

        <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IEnumerable&lt;Todo&gt;&gt; GetAllAsync()
        {
            <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _context.Todos.ToListAsync();
            <span class="hljs-keyword">if</span> (todo == <span class="hljs-literal">null</span>)
            {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Exception(<span class="hljs-string">" No Todo items found"</span>);
            }
            <span class="hljs-keyword">return</span> todo;

        }
        <span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">DeleteTodoAsync</span>(<span class="hljs-params">Guid id</span>)</span>
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
        }

        <span class="hljs-comment">// Get all TODO Items from the database </span>


        <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;Todo&gt; <span class="hljs-title">GetByIdAsync</span>(<span class="hljs-params">Guid id</span>)</span>
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">UpdateTodoAsync</span>(<span class="hljs-params">Guid id, UpdateTodoRequest request</span>)</span>
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
        }
    }
}
</code></pre>
<p>Now we have implemented the <code>CreateTodoAsync</code> and <code>GetAllAsync</code> methods in the <code>TodoServices</code> class. Before we proceed to implement the remaining methods, let's create routes for our API in the Controllers folder. So now let's create the TodoController class.</p>
<h2 id="step-11"> Step 11: Create the TodoController Class  </h2>

<p>In ASP.NET Core, controllers are responsible for handling incoming HTTP requests and sending responses. They serve as the entry point for our API, defining the routes and actions that clients can interact with.</p>
<p>Let's create a new file named <code>TodoController.cs</code> in the <code>Controllers</code> folder and add the following code:</p>
<pre><code class="lang-csharp">

<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Mvc;
<span class="hljs-keyword">using</span> TodoAPI.Interface;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">TodoAPI.Controllers</span>
{
    [<span class="hljs-meta">ApiController</span>]
    [<span class="hljs-meta">Route(<span class="hljs-meta-string">"api/[controller]"</span>)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TodoController</span> : <span class="hljs-title">ControllerBase</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ITodoServices _todoServices;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TodoController</span>(<span class="hljs-params">ITodoServices todoServices</span>)</span>
        {
            _todoServices = todoServices;
        }

    }
}
</code></pre>
<p>The <code>TodoController</code> class inherits from <code>ControllerBase</code>, a base class provided by ASP.NET Core for creating controllers. We've also added a route prefix of <code>api/[controller]</code> to the controller, which will be used as the base route for all actions in the controller.</p>
<h2 id="step-12"> Step 12: Implement the CreateTodoAsync  Method in the TodoController Class  </h2>

<p>Now that we have our Controller class, let's implement the <code>CreateTodoAsync</code> method in the <code>TodoController</code> class. This method will handle the creation of new Todo items in our database.</p>
<p>Navigate to the <code>TodoController</code> class and add the following code to the <code>CreateTodoAsync</code> method:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Controllers/TodoController.cs</span>

<span class="hljs-comment">// ...</span>
  [<span class="hljs-meta">HttpPost</span>]
  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">CreateTodoAsync</span>(<span class="hljs-params">CreateTodoRequest request</span>)</span>
  {
      <span class="hljs-keyword">if</span> (!ModelState.IsValid)
      {
          <span class="hljs-keyword">return</span> BadRequest(ModelState);
      }


      <span class="hljs-keyword">try</span>
      {

          <span class="hljs-keyword">await</span> _todoServices.CreateTodoAsync(request);
          <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">"Blog post successfully created"</span> });

      }
      <span class="hljs-keyword">catch</span> (Exception ex)
      {
          <span class="hljs-keyword">return</span> StatusCode(<span class="hljs-number">500</span>, <span class="hljs-keyword">new</span> { message = <span class="hljs-string">"An error occurred while creating the  crating Todo Item"</span>, error = ex.Message });

      }
  }
  <span class="hljs-comment">// ...</span>
</code></pre>
<p>Here's a breakdown of the <code>CreateTodoAsync</code> method:</p>
<ul>
<li><p><strong>Model Validation</strong>: We check if the request model is valid using <code>ModelState.IsValid</code>. If the model is not valid, we return a <code>BadRequest</code> response with the model state errors.</p>
</li>
<li><p><strong>Creating a Todo Item</strong>: We call the <code>CreateTodoAsync</code> method from the <code>ITodoServices</code> interface to create a new Todo item in the database.</p>
</li>
<li><p><strong>Success Response</strong>: If the Todo item is created successfully, we return an <code>Ok</code> response with a success message.</p>
</li>
<li><p><strong>Error Handling</strong>: If an error occurs during the creation process, we return a <code>500 Internal Server Error</code> response with an error message.</p>
</li>
</ul>
<p>Now let's implement the <code>GetAllAsync</code> method in the <code>TodoController</code> class. This method will retrieve all Todo items from the database.</p>
<p>Navigate to the <code>TodoController</code> class and add the following code to the <code>GetAllAsync</code> method:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Controllers/TodoController.cs </span>

<span class="hljs-comment">// ...</span>

  [<span class="hljs-meta">HttpGet</span>]
  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">GetAllAsync</span>(<span class="hljs-params"></span>)</span>
  {
      <span class="hljs-keyword">try</span>
      {
          <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _todoServices.GetAllAsync();
          <span class="hljs-keyword">if</span> (todo == <span class="hljs-literal">null</span> || !todo.Any())
          {
              <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">"No Todo Items  found"</span> });
          }
          <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">"Successfully retrieved all blog posts"</span>, data = todo });

      }
      <span class="hljs-keyword">catch</span> (Exception ex)
      {
          <span class="hljs-keyword">return</span> StatusCode(<span class="hljs-number">500</span>, <span class="hljs-keyword">new</span> { message = <span class="hljs-string">"An error occurred while retrieving all Tood it posts"</span>, error = ex.Message });


      }
  }

<span class="hljs-comment">// ...</span>
</code></pre>
<p>Here's a breakdown of the <code>GetAllAsync</code> method:</p>
<ul>
<li><p><strong>Retrieving Todo Items</strong>: We call the <code>GetAllAsync</code> method from the <code>ITodoServices</code> interface to fetch all Todo items from the database.</p>
</li>
<li><p><strong>Success Response</strong>: If Todo items are retrieved successfully, we return an <code>Ok</code> response with a success message and the list of Todo items.</p>
</li>
<li><p><strong>Error Handling</strong>: If an error occurs during the retrieval process, we return a <code>500 Internal Server Error</code> response with an error message.</p>
</li>
</ul>
<p>Now your <code>TodoController</code> class should look like this:</p>
<pre><code class="lang-csharp">


<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Mvc;
<span class="hljs-keyword">using</span> TodoAPI.Contracts;
<span class="hljs-keyword">using</span> TodoAPI.Interface;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">TodoAPI.Controllers</span>
{
    [<span class="hljs-meta">ApiController</span>]
    [<span class="hljs-meta">Route(<span class="hljs-meta-string">"api/[controller]"</span>)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TodoController</span> : <span class="hljs-title">ControllerBase</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ITodoServices _todoServices;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TodoController</span>(<span class="hljs-params">ITodoServices todoServices</span>)</span>
        {
            _todoServices = todoServices;
        }



<span class="hljs-comment">// Creating new Todo Item</span>
[<span class="hljs-meta">HttpPost</span>]
  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">CreateTodoAsync</span>(<span class="hljs-params">CreateTodoRequest request</span>)</span>
  {
      <span class="hljs-keyword">if</span> (!ModelState.IsValid)
      {
          <span class="hljs-keyword">return</span> BadRequest(ModelState);
      }


      <span class="hljs-keyword">try</span>
      {

          <span class="hljs-keyword">await</span> _todoServices.CreateTodoAsync(request);
          <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">"Blog post successfully created"</span> });

      }
      <span class="hljs-keyword">catch</span> (Exception ex)
      {
          <span class="hljs-keyword">return</span> StatusCode(<span class="hljs-number">500</span>, <span class="hljs-keyword">new</span> { message = <span class="hljs-string">"An error occurred while creating the  crating Todo Item"</span>, error = ex.Message });

      }
  }

    <span class="hljs-comment">// Get all Todo Items</span>

      [<span class="hljs-meta">HttpGet</span>]
  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">GetAllAsync</span>(<span class="hljs-params"></span>)</span>
  {
      <span class="hljs-keyword">try</span>
      {
          <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _todoServices.GetAllAsync();
          <span class="hljs-keyword">if</span> (todo == <span class="hljs-literal">null</span> || !todo.Any())
          {
              <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">"No Todo Items  found"</span> });
          }
          <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">"Successfully retrieved all blog posts"</span>, data = todo });

      }
      <span class="hljs-keyword">catch</span> (Exception ex)
      {
          <span class="hljs-keyword">return</span> StatusCode(<span class="hljs-number">500</span>, <span class="hljs-keyword">new</span> { message = <span class="hljs-string">"An error occurred while retrieving all Tood it posts"</span>, error = ex.Message });


      }
  }

    }
}
</code></pre>
<p>At this point, we've implemented the <code>CreateTodoAsync</code> and <code>GetAllAsync</code> methods in the <code>TodoController</code> class. These methods allow us to create new Todo items and retrieve all Todo items from the database. Let's try to run the application and see if everything is working fine.</p>
<p>Run the application by running the following command:</p>
<pre><code class="lang-bash">

dotnet run
</code></pre>
<p>If you see the following output, it means your application is running successfully:</p>
<pre><code class="lang-bash">
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5086
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: E:\Todo\TodoAPI4
</code></pre>
<p>While we'll be using Postman within Visual Studio Code for making API requests, it's worth noting that .NET 8 includes a built-in Swagger UI. This feature allows us to interact with our API endpoints directly from a web browser. To access the Swagger UI, open your browser and navigate to <code>https://localhost:5086/swagger/index.html</code>. You should see a page similar to the one below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/SwaggerUI.png" alt="SwaggerUI" width="600" height="400" loading="lazy">
This indicates that we've made significant progress. We've created an API that can create and retrieve Todo items. Let's test this by attempting to create a new Todo item using our API.</p>
<p>Open Postman and create a new POST request with the following URL: <code>https://localhost:5086/api/todo</code>. Set the request body to the following JSON object:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Learn ASP.NET Core"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Learn how to build web applications with ASP.NET Core"</span>,
    <span class="hljs-attr">"dueDate"</span>: <span class="hljs-string">"2022-12-31T00:00:00"</span>,
    <span class="hljs-attr">"priority"</span>: <span class="hljs-number">5</span>
}
</code></pre>
<p>Upon executing this request, you may encounter an error. This is because we haven't yet added our connection string to the <code>appsettings.json</code> file. Let's rectify this.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/PostmanError.png" alt="PostmanError" width="600" height="400" loading="lazy"></p>
<p><strong>Note</strong>: The error above is due to the absence of a connection string in the <code>appsettings.json</code> file. Let's add the connection string to the <code>appsettings.json</code> file.</p>
<p>Before we do that, let's setup or SQL Server Database. First Open your SQL Server Management Studio and you should see the below screen:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/SQLServerManagementStudio.png" alt="SQLServerManagementStudio" width="600" height="400" loading="lazy"></p>
<p>To connect to the SQL Server, where is says <code>Server Name</code> you can type <code>localhost</code> or <code>.</code> and click on the <code>Connect</code> button.</p>
<p>After connecting to the SQL Server, you will see the following screen:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/SQLServerManagementStudio2.png" alt="SQLServerManagementStudio2" width="600" height="400" loading="lazy"></p>
<p>Now go to your <code>appsettings.json</code> file and add the following connection string:</p>
<pre><code class="lang-json">
<span class="hljs-comment">//appsettings.json</span>
{
  <span class="hljs-attr">"DbSettings"</span>: {
    <span class="hljs-attr">"ConnectionString"</span>: <span class="hljs-string">"Server=localhost;Database=TodoAPIDb;  Integrated Security=true;  TrustServerCertificate=true;"</span>
  },
  <span class="hljs-attr">"Logging"</span>: {
    <span class="hljs-attr">"LogLevel"</span>: {
      <span class="hljs-attr">"Default"</span>: <span class="hljs-string">"Information"</span>,
      <span class="hljs-attr">"Microsoft"</span>: <span class="hljs-string">"Warning"</span>,
      <span class="hljs-attr">"Microsoft.Hosting.Lifetime"</span>: <span class="hljs-string">"Information"</span>
    },
    <span class="hljs-attr">"AllowedHosts"</span>: <span class="hljs-string">"*"</span>
  }
}
</code></pre>
<p>Let me explain the connection string above:</p>
<ul>
<li><code>Server</code>: This is the server name where the database is hosted. In this case, we're using <code>localhost</code> to connect to the local SQL Server instance.</li>
<li><code>Database</code>: This is the name of the database we want to connect to. We've set it to <code>TodoAPIDb</code>.</li>
<li><code>Integrated Security</code>: This parameter specifies that we're using Windows authentication to connect to the database.</li>
<li><code>TrustServerCertificate</code>: This parameter specifies that we trust the server certificate when connecting to the database.</li>
</ul>
<p>Now we need to register our <code>Service</code> and <code>Iservices</code> in the <code>Program.cs</code> file.</p>
<p>Add the service to the <code>Program.cs</code> file:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Program.cs</span>

<span class="hljs-comment">// ...</span>

builder.Services.AddScoped&lt;ITodoServices, TodoServices&gt;();

<span class="hljs-comment">// ...</span>
</code></pre>
<h2 id="step-13"> Step 13: Implement Migrations and Update the Database  </h2>

<p>Migrations in Entity Framework Core provide a mechanism to keep the database schema in sync with the application's data model. They generate SQL scripts that can be applied to the database to reflect changes in the data model, eliminating the need for manual database schema updates.</p>
<p>To create a migration, ensure you're in the root directory of your project and run the following command in the terminal:</p>
<pre><code class="lang-bash">dotnet ef migrations add InitialCreate
</code></pre>
<p>Upon successful execution, you'll see an output similar to the following:</p>
<pre><code class="lang-bash">dotnet ef migrations add InitialCreate
Build started...
Build succeeded.
Done. To undo this action, use <span class="hljs-string">'ef migrations remove'</span>
</code></pre>
<p>This command generates a new migration named <code>InitialCreate</code>, which contains SQL scripts derived from the changes in our data model. A new folder named <code>Migrations</code> will appear in your project directory.</p>
<p>To apply the migration and update the database, execute the following command:</p>
<pre><code class="lang-bash">dotnet ef database update
</code></pre>
<p>You might encounter an error like this:</p>
<pre><code class="lang-bash">  at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabase.&lt;&gt;c__DisplayClass0_0.&lt;.ctor&gt;b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
Only the invariant culture is supported <span class="hljs-keyword">in</span> globalization-invariant mode. See https://aka.ms/GlobalizationInvariantMode <span class="hljs-keyword">for</span> more information. (Parameter <span class="hljs-string">'name'</span>)
en-us is an invalid culture identifier.
</code></pre>
<p>This error indicates that the <code>en-us</code> culture is not supported in globalization-invariant mode. To resolve this, open the <code>TodoAPI.csproj</code> file and change <code>&lt;InvariantGlobalization&gt;true&lt;/InvariantGlobalization&gt;</code> to <code>&lt;InvariantGlobalization&gt;false&lt;/InvariantGlobalization&gt;</code>.</p>
<p>After making this change, run the <code>dotnet ef database update</code> command again. If the migration is successful, you'll see an output similar to the following:</p>
<pre><code class="lang-bash">
Build started...
Build succeeded.
Applying migration <span class="hljs-string">'20240518180222_InitialCreate'</span>.
Done.
</code></pre>
<p>This indicates that the migration has been applied successfully, and the database has been updated with the necessary schema changes.</p>
<p>Congratulations! You've successfully created a migration and updated the database schema. Now, let's test our API by creating a new Todo item using Postman.</p>
<h2 id="step-14">  Step 14: Verify Your API with Postman  </h2>

<p>Before we can interact with our API, we need to ensure that our application is up and running. Start the application by executing the following command in the terminal:</p>
<pre><code class="lang-bash">dotnet run
</code></pre>
<p>With the application running, we can now use Postman to send requests to our API. Let's create a new Todo item:</p>
<ol>
<li>Open Postman and create a new request.</li>
<li>Set the request method to <code>POST</code>.</li>
<li>Enter the following URL: <code>https://localhost:5086/api/todo</code>.</li>
<li>In the <code>Headers</code> tab, set the <code>Content-Type</code> to <code>application/json</code>.</li>
<li>In the <code>Body</code> tab, select <code>raw</code> and enter the following JSON object:</li>
</ol>
<pre><code class="lang-json">{
    <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Learn ASP.NET Core"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Learn how to build web applications with ASP.NET Core"</span>,
    <span class="hljs-attr">"dueDate"</span>: <span class="hljs-string">"2022-12-31T00:00:00"</span>,
    <span class="hljs-attr">"priority"</span>: <span class="hljs-number">5</span>
}
</code></pre>
<ol start="6">
<li>Click on the <code>Send</code> button to execute the request.</li>
</ol>
<p>If the request is successful, you'll receive a response similar to the one below:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Todo item successfully created"</span>
}
</code></pre>
<p>The image below illustrates the successful creation of a new Todo item using Postman:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/PostmanSuccess.png" alt="PostmanSuccess" width="600" height="400" loading="lazy"></p>
<p>Now that we've successfully created a new Todo item, let's retrieve all Todo items from the database using our API.</p>
<h2 id="step-15">   Step 15: Retrieve All Todo Items  </h2>

<p>To retrieve all Todo items from the database, follow these steps:</p>
<ol>
<li><p>Open Postman and create a new request.</p>
</li>
<li><p>Set the request method to <code>GET</code>. </p>
</li>
<li><p>Enter the following URL: <code>https://localhost:5086/api/todo</code>.</p>
</li>
<li><p>Click on the <code>Send</code> button to execute the request.</p>
</li>
</ol>
<p>If the request is successful, you'll receive a response similar to the one below:</p>
<pre><code class="lang-json">
   {
    <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Successfully retrieved all blog posts"</span>,
    <span class="hljs-attr">"data"</span>: [
        {
            <span class="hljs-attr">"id"</span>: <span class="hljs-string">"e9898d1b-9ad3-4482-ad65-08dc77664fab"</span>,
            <span class="hljs-attr">"title"</span>: <span class="hljs-string">"string"</span>,
            <span class="hljs-attr">"description"</span>: <span class="hljs-string">"string"</span>,
            <span class="hljs-attr">"isComplete"</span>: <span class="hljs-literal">false</span>,
            <span class="hljs-attr">"dueDate"</span>: <span class="hljs-string">"2024-05-18T16:52:22.054Z"</span>,
            <span class="hljs-attr">"priority"</span>: <span class="hljs-number">5</span>,
            <span class="hljs-attr">"createdAt"</span>: <span class="hljs-string">"2024-05-18T18:14:08.1755565+00:00"</span>,
            <span class="hljs-attr">"updatedAt"</span>: <span class="hljs-string">"0001-01-01T00:00:00"</span>
        }
    ]
}
</code></pre>
<p>The image below illustrates the successful retrieval of all Todo items using Postman:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/PostmanGetAll.png" alt="PostmanGetAll" width="600" height="400" loading="lazy"></p>
<p>Congratulations! You've successfully created an API that can create and retrieve Todo items. This marks the completion of our Todo API project. You've learned how to set up a .NET Core project, define models, create a database context, implement a service layer, and create API endpoints. You've also learned how to use Postman to interact with your API and test its functionality.</p>
<p>Now let's move on to create the <code>GetByIdAsync</code>, <code>UpdateTodoAsync</code>, and <code>DeleteTodoAsync</code> methods in the <code>TodoServices</code> class and <code>TodoController</code> class.</p>
<h2 id="step-16">  Step 16: Implement the GetByIdAsync Method  </h2>

<p>The <code>GetByIdAsync</code> method retrieves a specific Todo item by its <code>Id</code>. We'll implement this method in both the <code>TodoServices</code> and <code>TodoController</code> classes.</p>
<h3 id="heading-the-todoservices-class">  The <code>TodoServices</code> Class  </h3>


<p>In the <code>TodoServices</code> class, add the following code to the <code>GetByIdAsync</code> method:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Services/TodoServices.cs</span>

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;Todo&gt; <span class="hljs-title">GetByIdAsync</span>(<span class="hljs-params">Guid id</span>)</span>
{
    <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _context.Todos.FindAsync(id);
    <span class="hljs-keyword">if</span> (todo == <span class="hljs-literal">null</span>)
    {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> KeyNotFoundException(<span class="hljs-string">$"No Todo item with Id <span class="hljs-subst">{id}</span> found."</span>);
    }
    <span class="hljs-keyword">return</span> todo;
}
</code></pre>
<p>This method uses Entity Framework Core's <code>FindAsync</code> method to fetch a Todo item by its <code>Id</code>. If no Todo item is found, it throws a <code>KeyNotFoundException</code> with a descriptive error message.</p>
<h3 id="heading-the-todocontroller-class">   The <code>TodoController</code> Class  </h3>


<p>In the <code>TodoController</code> class, add the following code to the <code>GetByIdAsync</code> method:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Controllers/TodoController.cs</span>

[<span class="hljs-meta">HttpGet(<span class="hljs-meta-string">"{id:guid}"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">GetByIdAsync</span>(<span class="hljs-params">Guid id</span>)</span>
{
    <span class="hljs-keyword">try</span>
    {
        <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _todoServices.GetByIdAsync(id);
        <span class="hljs-keyword">if</span> (todo == <span class="hljs-literal">null</span>)
        {
            <span class="hljs-keyword">return</span> NotFound(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">$"No Todo item with Id <span class="hljs-subst">{id}</span> found."</span> });
        }
        <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">$"Successfully retrieved Todo item with Id <span class="hljs-subst">{id}</span>."</span>, data = todo });
    }
    <span class="hljs-keyword">catch</span> (Exception ex)
    {
        <span class="hljs-keyword">return</span> StatusCode(<span class="hljs-number">500</span>, <span class="hljs-keyword">new</span> { message = <span class="hljs-string">$"An error occurred while retrieving the Todo item with Id <span class="hljs-subst">{id}</span>."</span>, error = ex.Message });
    }
}
</code></pre>
<p>This method calls the <code>GetByIdAsync</code> method from the <code>ITodoServices</code> interface to fetch a Todo item by its <code>Id</code>. If a Todo item is retrieved successfully, it returns an <code>Ok</code> response with a success message and the Todo item. If an error occurs during the retrieval process, it returns a <code>500 Internal Server Error</code> response with an error message.</p>
<h2 id="step-17">  Step 17: Implement the UpdateTodoAsync Method   </h2>

<p>The <code>UpdateTodoAsync</code> method in the <code>TodoServices</code> class modifies an existing Todo item in the database. Let's implement this method now.</p>
<p>Navigate to the <code>TodoServices</code> class and add the following code to the <code>UpdateTodoAsync</code> method:</p>
<pre><code class="lang-csharp">

<span class="hljs-comment">// Services/TodoServices.cs</span>

<span class="hljs-comment">// ...</span>

 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">UpdateTodoAsync</span>(<span class="hljs-params">Guid id, UpdateTodoRequest request</span>)</span>
 {
     <span class="hljs-keyword">try</span>
     {
         <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _context.Todos.FindAsync(id);
         <span class="hljs-keyword">if</span> (todo == <span class="hljs-literal">null</span>)
         {
             <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Exception(<span class="hljs-string">$"Todo item with id <span class="hljs-subst">{id}</span> not found."</span>);
         }

         <span class="hljs-keyword">if</span> (request.Title != <span class="hljs-literal">null</span>)
         {
             todo.Title = request.Title;
         }

         <span class="hljs-keyword">if</span> (request.Description != <span class="hljs-literal">null</span>)
         {
             todo.Description = request.Description;
         }

         <span class="hljs-keyword">if</span> (request.IsComplete != <span class="hljs-literal">null</span>)
         {
             todo.IsComplete = request.IsComplete.Value;
         }

         <span class="hljs-keyword">if</span> (request.DueDate != <span class="hljs-literal">null</span>)
         {
             todo.DueDate = request.DueDate.Value;
         }

         <span class="hljs-keyword">if</span> (request.Priority != <span class="hljs-literal">null</span>)
         {
             todo.Priority = request.Priority.Value;
         }

         todo.UpdatedAt = DateTime.Now;

         <span class="hljs-keyword">await</span> _context.SaveChangesAsync();
     }
     <span class="hljs-keyword">catch</span> (Exception ex)
     {
         _logger.LogError(ex, <span class="hljs-string">$"An error occurred while updating the todo item with id <span class="hljs-subst">{id}</span>."</span>);
         <span class="hljs-keyword">throw</span>;
     }
 }

<span class="hljs-comment">// ...</span>
</code></pre>
<p>Here's a breakdown of the <code>UpdateTodoAsync</code> method:</p>
<ul>
<li><p><strong>Retrieving a Specific Todo Item</strong>: We use Entity Framework Core's <code>FindAsync</code> method to fetch a Todo item by its <code>Id</code>.</p>
</li>
<li><p><strong>Updating the Todo Item</strong>: We update the Todo item properties based on the values provided in the <code>UpdateTodoRequest</code> object.</p>
</li>
<li><p><strong>Error Handling</strong>: If no Todo item is found with the specified <code>Id</code>, we throw an exception with a descriptive error message.</p>
</li>
</ul>
<p>Now let's implement the <code>UpdateTodoAsync</code> method in the <code>TodoController</code> class. This method will modify an existing Todo item in the database.</p>
<p>Navigate to the <code>TodoController</code> class and add the following code to the <code>UpdateTodoAsync</code> method:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Controllers/TodoController.cs</span>

<span class="hljs-comment">// ... </span>
   [<span class="hljs-meta">HttpPut(<span class="hljs-meta-string">"{id:guid}"</span>)</span>]

   <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">UpdateTodoAsync</span>(<span class="hljs-params">Guid id, UpdateTodoRequest request</span>)</span>
   {

       <span class="hljs-keyword">if</span> (!ModelState.IsValid)
       {
           <span class="hljs-keyword">return</span> BadRequest(ModelState);
       }

       <span class="hljs-keyword">try</span>
       {

           <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _todoServices.GetByIdAsync(id);
           <span class="hljs-keyword">if</span> (todo == <span class="hljs-literal">null</span>)
           {
               <span class="hljs-keyword">return</span> NotFound(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">$"Todo Item  with id <span class="hljs-subst">{id}</span> not found"</span> });
           }

           <span class="hljs-keyword">await</span> _todoServices.UpdateTodoAsync(id, request);
           <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">$" Todo Item  with id <span class="hljs-subst">{id}</span> successfully updated"</span> });

       }
       <span class="hljs-keyword">catch</span> (Exception ex)
       {
           <span class="hljs-keyword">return</span> StatusCode(<span class="hljs-number">500</span>, <span class="hljs-keyword">new</span> { message = <span class="hljs-string">$"An error occurred while updating blog post with id <span class="hljs-subst">{id}</span>"</span>, error = ex.Message });


       }


   }

<span class="hljs-comment">// ...</span>
</code></pre>
<p>Here's a breakdown of the <code>UpdateTodoAsync</code> method:</p>
<ul>
<li><p><strong>Model Validation</strong>: We check if the request model is valid using <code>ModelState.IsValid</code>. If the model is not valid, we return a <code>BadRequest</code> response with the model state errors.</p>
</li>
<li><p><strong>Retrieving a Specific Todo Item</strong>: We call the <code>GetByIdAsync</code> method from the <code>ITodoServices</code> interface to fetch a Todo item by its <code>Id</code>.</p>
</li>
<li><p><strong>Updating the Todo Item</strong>: If the Todo item is found, we call the <code>UpdateTodoAsync</code> method from the <code>ITodoServices</code> interface to update the Todo item.</p>
</li>
<li><p><strong>Success Response</strong>: If the Todo item is updated successfully, we return an <code>Ok</code> response with a success message.</p>
</li>
<li><p><strong>Error Handling</strong>: If an error occurs during the update process, we return a <code>500 Internal Server Error</code> response with an error message.</p>
</li>
</ul>
<h2 id="step-18"> Step 18: Implement the DeleteTodoAsync Method   </h2>

<p>The <code>DeleteTodoAsync</code> method in the <code>TodoServices</code> class removes a Todo item from the database. Let's implement this method now.</p>
<p>Navigate to the <code>TodoServices</code> class and add the following code to the <code>DeleteTodoAsync</code> method:</p>
<pre><code class="lang-csharp">

<span class="hljs-comment">// Services/TodoServices.cs</span>

<span class="hljs-comment">// ...</span>


 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">DeleteTodoAsync</span>(<span class="hljs-params">Guid id</span>)</span>
 {

     <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _context.Todos.FindAsync(id);
     <span class="hljs-keyword">if</span>(todo != <span class="hljs-literal">null</span>)
     {
          _context.Todos.Remove(todo);
         <span class="hljs-keyword">await</span> _context.SaveChangesAsync();

     }
     <span class="hljs-keyword">else</span>
     {
         <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Exception(<span class="hljs-string">$"No  item found with the id <span class="hljs-subst">{id}</span>"</span>);
     }


 }

<span class="hljs-comment">// ...</span>
</code></pre>
<p>Here's a breakdown of the <code>DeleteTodoAsync</code> method:</p>
<ul>
<li><p><strong>Retrieving a Specific Todo Item</strong>: We use Entity Framework Core's <code>FindAsync</code> method to fetch a Todo item by its <code>Id</code>.</p>
</li>
<li><p><strong>Removing the Todo Item</strong>: If the Todo item is found, we remove it from the <code>Todos</code> DbSet in our context and save the changes asynchronously.</p>
</li>
<li><p><strong>Error Handling</strong>: If no Todo item is found with the specified <code>Id</code>, we throw an exception with a descriptive error message.</p>
</li>
</ul>
<p>Now let's implement the <code>DeleteTodoAsync</code> method in the <code>TodoController</code> class. This method will remove a Todo item from the database.</p>
<p>Navigate to the <code>TodoController</code> class and add the following code to the <code>DeleteTodoAsync</code> method:</p>
<pre><code class="lang-csharp">
<span class="hljs-comment">// Controllers/TodoController.cs</span>

<span class="hljs-comment">// ...</span>

 [<span class="hljs-meta">HttpDelete(<span class="hljs-meta-string">"{id:guid}"</span>)</span>]
 <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">DeleteTodoAsync</span>(<span class="hljs-params">Guid id</span>)</span>
 {
     <span class="hljs-keyword">try</span>
     {
         <span class="hljs-keyword">await</span> _todoServices.DeleteTodoAsync(id);
         <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">$"Todo  with id <span class="hljs-subst">{id}</span> successfully deleted"</span> });

     }
     <span class="hljs-keyword">catch</span> (Exception ex)
     {
         <span class="hljs-keyword">return</span> StatusCode(<span class="hljs-number">500</span>, <span class="hljs-keyword">new</span> { message = <span class="hljs-string">$"An error occurred while deleting Todo Item  with id <span class="hljs-subst">{id}</span>"</span>, error = ex.Message });

     }
 }



<span class="hljs-comment">// ...</span>
</code></pre>
<p>Here's a breakdown of the <code>DeleteTodoAsync</code> method:</p>
<ul>
<li><p><strong>Removing the Todo Item</strong>: We call the <code>DeleteTodoAsync</code> method from the <code>ITodoServices</code> interface to remove a Todo item by its <code>Id</code>.</p>
</li>
<li><p><strong>Success Response</strong>: If the Todo item is deleted successfully, we return an <code>Ok</code> response with a success message.</p>
</li>
<li><p><strong>Error Handling</strong>: If an error occurs during the deletion process, we return a <code>500 Internal Server Error</code> response with an error message.</p>
</li>
</ul>
<p>Now your <code>TodoServices</code> class should look like this:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Services/TodoServices.cs</span>

<span class="hljs-keyword">using</span> AutoMapper;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;
<span class="hljs-keyword">using</span> TodoAPI.AppDataContext;
<span class="hljs-keyword">using</span> TodoAPI.Contracts;
<span class="hljs-keyword">using</span> TodoAPI.Interface;
<span class="hljs-keyword">using</span> TodoAPI.Models;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">TodoAPI.Services</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TodoServices</span> : <span class="hljs-title">ITodoServices</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> TodoDbContext _context;
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ILogger&lt;TodoServices&gt; _logger;
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IMapper _mapper;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TodoServices</span>(<span class="hljs-params">TodoDbContext context, ILogger&lt;TodoServices&gt; logger, IMapper mapper</span>)</span>
        {
            _context = context;
            _logger = logger;
            _mapper = mapper;
        }




        <span class="hljs-comment">//  Create Todo for it to be saved in the datbase </span>

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">CreateTodoAsync</span>(<span class="hljs-params">CreateTodoRequest request</span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-keyword">var</span> todo = _mapper.Map&lt;Todo&gt;(request);
                todo.CreatedAt = DateTime.Now;
                _context.Todos.Add(todo);
                <span class="hljs-keyword">await</span> _context.SaveChangesAsync();
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                _logger.LogError(ex, <span class="hljs-string">"An error occurred while creating the todo item."</span>);
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Exception(<span class="hljs-string">"An error occurred while creating the todo item."</span>);
            }
        }


        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;Todo&gt; <span class="hljs-title">GetByIdAsync</span>(<span class="hljs-params">Guid id</span>)</span>
        {
            <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _context.Todos.FindAsync(id);
            <span class="hljs-keyword">if</span> (todo == <span class="hljs-literal">null</span>)
            {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Exception(<span class="hljs-string">$" No Items with <span class="hljs-subst">{id}</span> found "</span>);
            }
            <span class="hljs-keyword">return</span> todo;
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">UpdateTodoAsync</span>(<span class="hljs-params">Guid id, UpdateTodoRequest request</span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _context.Todos.FindAsync(id);
                <span class="hljs-keyword">if</span> (todo == <span class="hljs-literal">null</span>)
                {
                    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Exception(<span class="hljs-string">$"Todo item with id <span class="hljs-subst">{id}</span> not found."</span>);
                }

                <span class="hljs-keyword">if</span> (request.Title != <span class="hljs-literal">null</span>)
                {
                    todo.Title = request.Title;
                }

                <span class="hljs-keyword">if</span> (request.Description != <span class="hljs-literal">null</span>)
                {
                    todo.Description = request.Description;
                }

                <span class="hljs-keyword">if</span> (request.IsComplete != <span class="hljs-literal">null</span>)
                {
                    todo.IsComplete = request.IsComplete.Value;
                }

                <span class="hljs-keyword">if</span> (request.DueDate != <span class="hljs-literal">null</span>)
                {
                    todo.DueDate = request.DueDate.Value;
                }

                <span class="hljs-keyword">if</span> (request.Priority != <span class="hljs-literal">null</span>)
                {
                    todo.Priority = request.Priority.Value;
                }

                todo.UpdatedAt = DateTime.Now;

                <span class="hljs-keyword">await</span> _context.SaveChangesAsync();
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                _logger.LogError(ex, <span class="hljs-string">$"An error occurred while updating the todo item with id <span class="hljs-subst">{id}</span>."</span>);
                <span class="hljs-keyword">throw</span>;
            }
        }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IEnumerable&lt;Todo&gt;&gt; GetAllAsync()
        {
            <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _context.Todos.ToListAsync();
            <span class="hljs-keyword">if</span> (todo == <span class="hljs-literal">null</span>)
            {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Exception(<span class="hljs-string">" No Todo items found"</span>);
            }
            <span class="hljs-keyword">return</span> todo;

        }
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">DeleteTodoAsync</span>(<span class="hljs-params">Guid id</span>)</span>
        {

            <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _context.Todos.FindAsync(id);
            <span class="hljs-keyword">if</span> (todo != <span class="hljs-literal">null</span>)
            {
                _context.Todos.Remove(todo);
                <span class="hljs-keyword">await</span> _context.SaveChangesAsync();

            }
            <span class="hljs-keyword">else</span>
            {
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Exception(<span class="hljs-string">$"No  item found with the id <span class="hljs-subst">{id}</span>"</span>);
            }


        }




    }
}
</code></pre>
<p>Now your <code>TodoController</code> class should look like this:</p>
<pre><code class="lang-csharp">
<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Mvc;
<span class="hljs-keyword">using</span> TodoAPI.Contracts;
<span class="hljs-keyword">using</span> TodoAPI.Interface;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">TodoAPI.Controllers</span>
{
    [<span class="hljs-meta">ApiController</span>]
    [<span class="hljs-meta">Route(<span class="hljs-meta-string">"api/[controller]"</span>)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TodoController</span> : <span class="hljs-title">ControllerBase</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ITodoServices _todoServices;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TodoController</span>(<span class="hljs-params">ITodoServices todoServices</span>)</span>
        {
            _todoServices = todoServices;
        }



        <span class="hljs-comment">// Creating new Todo Item</span>
        [<span class="hljs-meta">HttpPost</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">CreateTodoAsync</span>(<span class="hljs-params">CreateTodoRequest request</span>)</span>
        {
            <span class="hljs-keyword">if</span> (!ModelState.IsValid)
            {
                <span class="hljs-keyword">return</span> BadRequest(ModelState);
            }


            <span class="hljs-keyword">try</span>
            {

                <span class="hljs-keyword">await</span> _todoServices.CreateTodoAsync(request);
                <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">"Blog post successfully created"</span> });

            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                <span class="hljs-keyword">return</span> StatusCode(<span class="hljs-number">500</span>, <span class="hljs-keyword">new</span> { message = <span class="hljs-string">"An error occurred while creating the  crating Todo Item"</span>, error = ex.Message });

            }
        }

        <span class="hljs-comment">// Get all Todo Items</span>

        [<span class="hljs-meta">HttpGet</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">GetAllAsync</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _todoServices.GetAllAsync();
                <span class="hljs-keyword">if</span> (todo == <span class="hljs-literal">null</span> || !todo.Any())
                {
                    <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">"No Todo Items  found"</span> });
                }
                <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">"Successfully retrieved all blog posts"</span>, data = todo });

            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                <span class="hljs-keyword">return</span> StatusCode(<span class="hljs-number">500</span>, <span class="hljs-keyword">new</span> { message = <span class="hljs-string">"An error occurred while retrieving all Tood it posts"</span>, error = ex.Message });


            }
        }

        [<span class="hljs-meta">HttpGet(<span class="hljs-meta-string">"{id:guid}"</span>)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">GetByIdAsync</span>(<span class="hljs-params">Guid id</span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {

                <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _todoServices.GetByIdAsync(id);
                <span class="hljs-keyword">if</span> (todo == <span class="hljs-literal">null</span>)
                {
                    <span class="hljs-keyword">return</span> NotFound(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">$"Now Todo item with id <span class="hljs-subst">{id}</span> not found"</span> });

                }
                <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">$"Successfully retrieved  todo item with id <span class="hljs-subst">{id}</span>"</span>, data = todo });

            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                <span class="hljs-keyword">return</span> StatusCode(<span class="hljs-number">500</span>, <span class="hljs-keyword">new</span> { message = <span class="hljs-string">$"An error occurred while retrieving   todo item  with id <span class="hljs-subst">{id}</span>"</span>, error = ex.Message });

            }
        }



        [<span class="hljs-meta">HttpPut(<span class="hljs-meta-string">"{id:guid}"</span>)</span>]

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">UpdateTodoAsync</span>(<span class="hljs-params">Guid id, UpdateTodoRequest request</span>)</span>
        {

            <span class="hljs-keyword">if</span> (!ModelState.IsValid)
            {
                <span class="hljs-keyword">return</span> BadRequest(ModelState);
            }

            <span class="hljs-keyword">try</span>
            {

                <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">await</span> _todoServices.GetByIdAsync(id);
                <span class="hljs-keyword">if</span> (todo == <span class="hljs-literal">null</span>)
                {
                    <span class="hljs-keyword">return</span> NotFound(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">$"Todo Item  with id <span class="hljs-subst">{id}</span> not found"</span> });
                }

                <span class="hljs-keyword">await</span> _todoServices.UpdateTodoAsync(id, request);
                <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">$" Todo Item  with id <span class="hljs-subst">{id}</span> successfully updated"</span> });

            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                <span class="hljs-keyword">return</span> StatusCode(<span class="hljs-number">500</span>, <span class="hljs-keyword">new</span> { message = <span class="hljs-string">$"An error occurred while updating blog post with id <span class="hljs-subst">{id}</span>"</span>, error = ex.Message });


            }


        }


        [<span class="hljs-meta">HttpDelete(<span class="hljs-meta-string">"{id:guid}"</span>)</span>]
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">DeleteTodoAsync</span>(<span class="hljs-params">Guid id</span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-keyword">await</span> _todoServices.DeleteTodoAsync(id);
                <span class="hljs-keyword">return</span> Ok(<span class="hljs-keyword">new</span> { message = <span class="hljs-string">$"Todo  with id <span class="hljs-subst">{id}</span> successfully deleted"</span> });

            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                <span class="hljs-keyword">return</span> StatusCode(<span class="hljs-number">500</span>, <span class="hljs-keyword">new</span> { message = <span class="hljs-string">$"An error occurred while deleting Todo Item  with id <span class="hljs-subst">{id}</span>"</span>, error = ex.Message });

            }
        }


    }
}
</code></pre>
<p>Now that we've implemented the <code>GetByIdAsync</code>, <code>UpdateTodoAsync</code>, and <code>DeleteTodoAsync</code> methods in the <code>TodoServices</code> and <code>TodoController</code> classes, we can test our API to ensure that everything is working as expected.</p>
<h2 id="step-19"> Step 19: Test Your API Endpoints with Postman   </h2>

<p>With our application up and running, we can now test all our API endpoints. We'll create new Todo items, retrieve all Todo items, fetch a specific Todo item by its <code>Id</code>, update a Todo item, and delete a Todo item using Postman. Let's start by creating three new Todo items.</p>
<p>Note that we'll be creating these Todo items one at a time, not all at once. Follow these steps for each Todo item:</p>
<ol>
<li>Open Postman and create a new request.</li>
<li>Set the request method to <code>POST</code>.</li>
<li>Enter the following URL: <code>http://localhost:5086/api/todo</code>.</li>
<li>In the <code>Headers</code> tab, set the <code>Content-Type</code> to <code>application/json</code>.</li>
<li>In the <code>Body</code> tab, select <code>raw</code> and enter one of the following JSON objects:</li>
</ol>
<p>For the first Todo item:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Learn ASP.NET Core"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Learn how to build web applications with ASP.NET Core"</span>,
    <span class="hljs-attr">"dueDate"</span>: <span class="hljs-string">"2022-12-31T00:00:00"</span>,
    <span class="hljs-attr">"priority"</span>: <span class="hljs-number">2</span>
}
</code></pre>
<p>For the second Todo item:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Learn C#"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Learn how to build web applications with C#"</span>,
    <span class="hljs-attr">"dueDate"</span>: <span class="hljs-string">"2022-12-31T00:00:00"</span>,
    <span class="hljs-attr">"priority"</span>: <span class="hljs-number">3</span>
}
</code></pre>
<p>For the third Todo item:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Learn SQL"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Learn how to build web applications with SQL"</span>,
    <span class="hljs-attr">"dueDate"</span>: <span class="hljs-string">"2022-12-31T00:00:00"</span>,
    <span class="hljs-attr">"priority"</span>: <span class="hljs-number">1</span>
}
</code></pre>
<ol start="6">
<li>Click on the <code>Send</code> button to execute the request for each Todo item.</li>
</ol>
<p>If each request is successful, you'll receive a response similar to the one below:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Todo item successfully created"</span>
}
</code></pre>
<p>This indicates that the Todo item has been successfully created. Repeat the steps for each Todo item.</p>
<h3 id="heading-how-to-retrieve-all-todo-items"> How to Retrieve All Todo Items   </h3>

<p>To fetch all Todo items from the database, follow these steps:</p>
<ol>
<li>Launch Postman and initiate a new request.</li>
<li>Set the HTTP method to <code>GET</code>.</li>
<li>Input the following URL: <code>http://localhost:5086/api/todo</code>.</li>
<li>Click the <code>Send</code> button to execute the request.</li>
</ol>
<p>The image below demonstrates a successful retrieval of all Todo items using Postman:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/PostmanGetAll-1.png" alt="PostmanGetAll-1" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-fetch-a-specific-todo-item-by-id">  How to Fetch a Specific Todo Item by Id  </h3>

<p>To retrieve a specific Todo item using its <code>Id</code>, follow these steps:</p>
<ol>
<li>Launch Postman and initiate a new request.</li>
<li>Set the HTTP method to <code>GET</code>.</li>
<li>Input the following URL: <code>http://localhost:5086/api/todo/{id}</code>, replacing <code>{id}</code> with the <code>Id</code> of the Todo item you wish to retrieve. For example, <code>http://localhost:5086/api/todo/e9898d1b-9ad3-4482-ad65-08dc77664fab</code>.</li>
<li>Click the <code>Send</code> button to execute the request.</li>
</ol>
<p>Upon successful execution, you'll receive a response similar to the one below:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Successfully retrieved  todo item with id e9898d1b-9ad3-4482-ad65-08dc77664fab"</span>,
    <span class="hljs-attr">"data"</span>: {
        <span class="hljs-attr">"id"</span>: <span class="hljs-string">"e9898d1b-9ad3-4482-ad65-08dc77664fab"</span>,
        <span class="hljs-attr">"title"</span>: <span class="hljs-string">"string"</span>,
        <span class="hljs-attr">"description"</span>: <span class="hljs-string">"string"</span>,
        <span class="hljs-attr">"isComplete"</span>: <span class="hljs-literal">false</span>,
        <span class="hljs-attr">"dueDate"</span>: <span class="hljs-string">"2024-05-18T16:52:22.054"</span>,
        <span class="hljs-attr">"priority"</span>: <span class="hljs-number">5</span>,
        <span class="hljs-attr">"createdAt"</span>: <span class="hljs-string">"2024-05-18T18:14:08.1755565"</span>,
        <span class="hljs-attr">"updatedAt"</span>: <span class="hljs-string">"0001-01-01T00:00:00"</span>
    }
}
</code></pre>
<p>The image below demonstrates the successful retrieval of a specific Todo item using Postman:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/PostmanGetById.png" alt="PostmanGetById" width="600" height="400" loading="lazy">)</p>
<h3 id="heading-how-to-update-a-todo-item">   How to Update a Todo Item </h3>


<p>In our Todo model, we have a property <code>isComplete</code> which is initially set to <code>false</code> when a Todo item is created. This property is used to indicate whether a task has been completed or not. To mark a task as complete, we need to update this property to <code>true</code>. Note that we can only update one Todo item at a time, and we identify the item to update by its <code>Id</code>.</p>
<p>Let's fetch all the Todo items, select one and update it by setting the <code>isComplete</code> property to <code>true</code>.</p>
<p>Follow these steps to update a Todo item:</p>
<ol>
<li>Launch Postman and initiate a new request.</li>
<li>Set the HTTP method to <code>PUT</code>.</li>
<li>Input the following URL: <code>http://localhost:5086/api/todo/{id}</code>, replacing <code>{id}</code> with the <code>Id</code> of the Todo item you wish to update. For example, <code>http://localhost:5086/api/todo/e9898d1b-9ad3-4482-ad65-08dc77664fab</code>.</li>
<li>In the <code>Headers</code> tab, set the <code>Content-Type</code> to <code>application/json</code>.</li>
<li>In the <code>Body</code> tab, select <code>raw</code> and enter the following JSON object:</li>
</ol>
<pre><code class="lang-json">{
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"21ebe2c2-79c0-45d4-4139-08dc789e3eb2"</span>,
    <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Learn C#"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Learn how to build web applications with C#"</span>,
    <span class="hljs-attr">"isComplete"</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// Set the isComplete to true</span>
    <span class="hljs-attr">"dueDate"</span>: <span class="hljs-string">"2022-12-31T00:00:00"</span>,
    <span class="hljs-attr">"priority"</span>: <span class="hljs-number">3</span>,
    <span class="hljs-attr">"createdAt"</span>: <span class="hljs-string">"2024-05-20T07:27:39.3730049+00:00"</span>,
    <span class="hljs-attr">"updatedAt"</span>: <span class="hljs-string">"0001-01-01T00:00:00"</span>
}
</code></pre>
<ol start="6">
<li>Click the <code>Send</code> button to execute the request.</li>
</ol>
<p>Upon successful execution, you'll receive a response similar to the one below:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Todo Item with id 21ebe2c2-79c0-45d4-4139-08dc789e3eb2 successfully updated"</span>
}
</code></pre>
<p>The image below demonstrates the successful update of a Todo item using Postman:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/PostmanUpdate.png" alt="PostmanUpdate" width="600" height="400" loading="lazy"></p>
<p><strong>Note</strong>: The <code>isComplete</code> property of the Todo item has been updated to <code>true</code>. Now, when you fetch all Todo items from the database, you will see the <code>isComplete</code> property is <code>true</code> for the updated Todo item.</p>
<p>Now let's see how to delete a Todo item from the database.</p>
<h3 id="heading-how-to-delete-a-todo-item">  How to Delete a Todo Item </h3>


<p>To remove a Todo item from the database, follow these steps: </p>
<ol>
<li>Open Postman and create a new request.</li>
<li>Set the HTTP method to <code>DELETE</code>.</li>
<li>Enter the following URL: <code>http://localhost:5086/api/todo/{id}</code>, replacing <code>{id}</code> with the <code>Id</code> of the Todo item you intend to remove. For instance, <code>http://localhost:5086/api/todo/e9898d1b-9ad3-4482-ad65-08dc77664fab</code>.</li>
<li>Click the <code>Send</code> button to execute the request.</li>
</ol>
<p>If the request is successful, you'll receive a response similar to the one below:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Todo item with id 21ebe2c2-79c0-45d4-4139-08dc789e3eb2 successfully deleted"</span>
}
</code></pre>
<p>The image below illustrates the successful deletion of a Todo item using Postman:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/PostmanDelete.png" alt="PostmanDelete" width="600" height="400" loading="lazy"></p>
<p>Well done! You've successfully implemented the <code>GetByIdAsync</code>, <code>UpdateTodoAsync</code>, and <code>DeleteTodoAsync</code> methods in the <code>TodoServices</code> and <code>TodoController</code> classes. You've also verified your API endpoints using Postman to ensure they're functioning as expected. You can </p>
<h3 id="heading-source-code">   Source Code  </h3>


<p>The entire source code for this project is readily available in the <a target="_blank" href="https://github.com/Clifftech123/TodoAPI">TodoAPI</a> GitHub repository. I encourage you to delve into the codebase, tinker with various functionalities, and bolster your proficiency in crafting APIs using ASP.NET Core 8.</p>
<h2 id="conclusion"> Conclusion  </h2>

<p>In this guide, we've journeyed through the process of constructing a robust Todo API using the power of ASP.NET Core 8. We initiated our project from scratch, meticulously defining the essential models that form the backbone of our Todo application.</p>
<p>We then created a database context, a crucial step that facilitated our interaction with the database. To further streamline this interaction, we implemented a service layer, effectively abstracting the complexities of direct database operations.</p>
<p>Next, we created our API endpoints. These endpoints serve as the gateways for <code>creating</code>, <code>retrieving</code>, <code>updating</code>, and <code>deleting</code> Todo items, thereby providing comprehensive functionality to our application.</p>
<p>The final stage of our journey involved rigorous testing of our API using Postman. This ensured that our application was not only built as per our design but also functioned as expected, providing a reliable and efficient service.</p>
<p>As we conclude, it's important to remember that the knowledge gained here forms a solid foundation for building more complex and feature-rich APIs. The journey of learning and exploration doesn't end here – it's just the beginning. Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Prevent Web API Attacks with Data Validation – Web API Security Guide ]]>
                </title>
                <description>
                    <![CDATA[ Adequate data protection and user confidentiality are key responsibilities for web developers. Hence, it is important to ensure the highest possible security while building API endpoints. The act of application security is a shared responsibility amo... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/web-api-security-guide/</link>
                <guid isPermaLink="false">66bb58d7965d5c9ed5487ba4</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Validation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Security ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwatobi ]]>
                </dc:creator>
                <pubDate>Wed, 03 Apr 2024 09:13:45 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/04/apidat.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Adequate data protection and user confidentiality are key responsibilities for web developers. Hence, it is important to ensure the highest possible security while building API endpoints.</p>
<p>The act of application security is a shared responsibility amongst the client and server developers and negligence of one’s role can be disastrous. <a target="_blank" href="https://www.statista.com/statistics/1307426/number-of-data-breaches-worldwide/#:~:text=During%20the%20fourth%20quarter%20of,concerns%20of%20company%20leaders%20worldwide.">Statistics</a> show that data breaches in 2023 resulted in exposure of over 8 million data records worldwide.</p>
<p>In this article, I'll be highlighting key areas of API security, which involves data validation.  This concept is quite crucial in helping you protect your API from web attacks via malicious user data. This tutorial is well-suited for all backend developers regardless of years of experience.</p>
<p>To be able to follow this tutorial, here are some prerequisites:</p>
<ul>
<li>Knowledge of Node.js</li>
<li>Knowledge of npm and package installation</li>
</ul>
<p>With that in place, let’s get started.</p>
<h2 id="heading-how-does-data-validation-work">How Does Data Validation Work?</h2>
<p>First of all, what is data validation? Data validation simply entails ensuring the accuracy and reliability of the data received from external sources before onward data processing. </p>
<p>It is a key component of web API security as it is essential for preventing the occurrence of web injection attacks, SQL attacks and NoSQL attacks. To know more about these, you can check this <a target="_blank" href="https://owasp.org/www-community/Injection_Flaws#:~:text=Description,connected%20to%20the%20vulnerable%20application.">link</a>.</p>
<p>Note that data validation is needed but not limited to the following backend operations.:</p>
<ul>
<li>User login and sign up</li>
<li>Response query</li>
<li>Updating server databases</li>
</ul>
<p>All these can be used as avenues by mischievous black hat hackers to gain access to the server database and obtain sensitive user details or even wreak havoc by formatting the entire database.</p>
<h2 id="heading-popular-data-validation-tools">Popular Data Validation Tools</h2>
<p>So far, there are lot of tools that can help the programmer achieve efficient data validation in API development. </p>
<p>They help you avoid reinventing the wheel of using long regex code to validate data. They provide a whole lot of features, including error handling and validation customization functionalities.  </p>
<p>Some of these tools include:<br>•    <a target="_blank" href="https://joi.dev">Joi</a><br>•    <a target="_blank" href="https://zod.dev/">Zod</a><br>•    <a target="_blank" href="https://www.npmjs.com/package/yup">Yup</a><br>•    <a target="_blank" href="https://ajv.js.org/">AJv</a><br>•    <a target="_blank" href="https://valibot.dev/">Valibot</a><br>•    <a target="_blank" href="https://www.npmjs.com/package/validatorjs">Validator.js</a><br>•    <a target="_blank" href="https://docs.superstructjs.org/guides/02-validating-data">Superstruct</a></p>
<p>To further shed light on these tools, we'll compare some of the most popular data validation tools mentioned above.</p>
<h2 id="heading-pros-and-cons-of-data-validation-tools">Pros and Cons of Data Validation Tools</h2>
<p>To further enlighten you about these JavaScript validation tools, I will be highlighting some pros and cons of three of these popular JavaScript validation tools.</p>
<h3 id="heading-joi">Joi</h3>
<h6 id="heading-pros">Pros</h6>
<ul>
<li>It has a strong, large user community and development support</li>
<li>It has built-in capabilities to handle complex validations</li>
</ul>
<h6 id="heading-cons">Cons</h6>
<ul>
<li>It’s syntax is quite verbose</li>
</ul>
<h3 id="heading-zod">Zod</h3>
<h6 id="heading-pros-1">Pros</h6>
<ul>
<li>It is easily compatible with Typescript projects</li>
<li>It has efficient error-handling capabilities</li>
</ul>
<h6 id="heading-cons-1">Cons</h6>
<ul>
<li>Async validation isn’t supported.</li>
</ul>
<h3 id="heading-yup">Yup</h3>
<h6 id="heading-pros-2">Pros</h6>
<ul>
<li>It mainly uses declarative syntax to set its validation tool which confers its simplicity</li>
<li>It has a comparable fast performance.</li>
</ul>
<h6 id="heading-cons-2">Cons</h6>
<ul>
<li>It doesn’t provide customization features</li>
<li>It has limited ability to handle complex validations</li>
</ul>
<p>For the purpose of this tutorial, we'll use Joi as our data validation tool.</p>
<h2 id="heading-introduction-to-joi">Introduction to Joi</h2>
<p>Joi is a simple and efficient JavaScript-based data validation tool that is based on the schema-type configuration.</p>
<p>It has built-in capabilities for validating the occurrence of data in various forms, but not limited to Booleans, strings, functions and intervals. It can also handle complex validation operations.</p>
<p>Additionally, it provides minimal caching functionalities. More information about the tool can be found <a target="_blank" href="https://joi.dev/api/?v=17.12.2">here</a>.</p>
<h2 id="heading-how-to-set-up-joi">How to Set Up Joi</h2>
<p>In this section, we'll set up Joi in our local environment. To install Joi, navigate to the code folder via the command line and run this:</p>
<pre><code class="lang-bash">npm i joi
</code></pre>
<p>A message confirming successful installation should be displayed. With that completed, we can demonstrate the power of Joi in validating user registration in our demo API.</p>
<h2 id="heading-demo-project">Demo Project</h2>
<p>In this project, you'll use Joi to validate the input received from the client with the intent to sign up on the server. The default code for the user sign-up function for the Node.js application can be found <a target="_blank" href="https://github.com/oluwatobi2001/location-backend/blob/master/Controller/Authentication.js">here</a>.</p>
<p>Go on and import the installed Joi package into your code:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> Joi = <span class="hljs-built_in">require</span>(<span class="hljs-string">"joi"</span>);
</code></pre>
<p>Before writing our signup controller, we'll initialize the Joi library within the code file:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> SignUpSchema = Joi.object({});
</code></pre>
<p>In this project, we'll validate the email, password and username parameters received from the client.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> SignUpSchema = Joi.object({
    <span class="hljs-attr">email</span>: Joi.string().email({
        <span class="hljs-attr">minDomainSegments</span>: <span class="hljs-number">2</span>,
        <span class="hljs-attr">tlds</span>: {
            <span class="hljs-attr">allow</span>: [<span class="hljs-string">'com'</span>, <span class="hljs-string">'net'</span>]
        }
    }),
    <span class="hljs-attr">username</span>: Joi.string().alphanum().min(<span class="hljs-number">3</span>).max(<span class="hljs-number">15</span>).required(),
    <span class="hljs-attr">password</span>: Joi.string().min(<span class="hljs-number">8</span>).required()
});
</code></pre>
<p>The email parameter object ensures that the email address is a string, and the domain site is limited to .com and .net, disallowing other forms of domains.</p>
<p>For the username parameter, it ensures that it is a string containing both letters and numbers with a minimum character count of 3 and a maximum character count of 15. The required function ensures that these conditions must be met or the entire request won't be validated.</p>
<p>The password parameter ensures that the password supplied is in a string format with a minimum character count of 8, and it is also required.</p>
<p>To apply it to our endpoints, we include this within the controller function:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { error, value } = SignUpSchema.validate(req.body, { <span class="hljs-attr">abortEarly</span>: <span class="hljs-literal">false</span> });
<span class="hljs-keyword">if</span> (error) {
    res.status(<span class="hljs-number">400</span>).json(error.details);
    <span class="hljs-keyword">return</span>;
}
</code></pre>
<p>This function gets executed before inserting the user details into the database. The schema tries to validate the received input and then proceeds to the database if successfully validated.</p>
<p>The <code>abortEarly</code> feature is included to allow for all parameters to be assessed. All the errors will be displayed if there is any.</p>
<p>The above can also be replicated in the Login controller function. You can also check out the <a target="_blank" href="https://joi.dev/api/?v=17.12.2">documentation</a> for other examples of complex validation options using Joi.</p>
<p>The final code for the project is displayed below:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">"jsonwebtoken"</span>);
<span class="hljs-keyword">const</span> userSchema = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../Schema/User"</span>);
<span class="hljs-keyword">const</span> Joi = <span class="hljs-built_in">require</span>(<span class="hljs-string">"joi"</span>);
<span class="hljs-keyword">const</span> bcrypt = <span class="hljs-built_in">require</span>(<span class="hljs-string">"bcrypt"</span>);
<span class="hljs-keyword">const</span> { createNewColumn, checkRecordsExists, insertRecord } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../utils/sqlSchemaFunction'</span>);

<span class="hljs-keyword">const</span> generateAccessToken = <span class="hljs-function">(<span class="hljs-params">use</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> jwt.sign({ <span class="hljs-attr">userID</span>: use }, process.env.JWT, { <span class="hljs-attr">expiresIn</span>: <span class="hljs-string">"1d"</span> });
}

<span class="hljs-keyword">const</span> SignUpSchema = Joi.object({
    <span class="hljs-attr">email</span>: Joi.string().email({ <span class="hljs-attr">minDomainSegments</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">tlds</span>: { <span class="hljs-attr">allow</span>: [<span class="hljs-string">'com'</span>, <span class="hljs-string">'net'</span>] } }),
    <span class="hljs-attr">username</span>: Joi.string().alphanum().min(<span class="hljs-number">3</span>).max(<span class="hljs-number">15</span>).required(),
    <span class="hljs-attr">password</span>: Joi.string().min(<span class="hljs-number">8</span>).required()
});

<span class="hljs-keyword">const</span> loginSchema = Joi.object({
    <span class="hljs-attr">email</span>: Joi.string().email({ <span class="hljs-attr">minDomainSegments</span>: <span class="hljs-number">2</span>, <span class="hljs-attr">tlds</span>: { <span class="hljs-attr">allow</span>: [<span class="hljs-string">'com'</span>, <span class="hljs-string">'net'</span>] } }),
    <span class="hljs-attr">password</span>: Joi.string().min(<span class="hljs-number">8</span>).required()
});

<span class="hljs-keyword">const</span> register = <span class="hljs-keyword">async</span> (req, res) =&gt; {
    <span class="hljs-keyword">const</span> email = req.body.email;
    <span class="hljs-keyword">const</span> password = req.body.password;

    <span class="hljs-keyword">if</span> (!email || !password) {
        res.status(<span class="hljs-number">400</span>).json(<span class="hljs-string">"Please supply the email or password"</span>);
        <span class="hljs-keyword">return</span>; 
    }

    <span class="hljs-keyword">const</span> { error, value } = SignUpSchema.validate(req.body, { <span class="hljs-attr">abortEarly</span>: <span class="hljs-literal">false</span> });
    <span class="hljs-keyword">if</span> (error) {
        res.status(<span class="hljs-number">400</span>).json(error.details);
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">const</span> salt = <span class="hljs-keyword">await</span> bcrypt.genSalt(<span class="hljs-number">10</span>);
    <span class="hljs-keyword">const</span> hashedPassword = <span class="hljs-keyword">await</span> bcrypt.hash(password, salt);
    <span class="hljs-keyword">const</span> user = {
        <span class="hljs-attr">username</span>: req.body.username,
        <span class="hljs-attr">email</span>: email,
        <span class="hljs-attr">password</span>: hashedPassword
    };

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> userAlreadyExists = <span class="hljs-keyword">await</span> checkRecordsExists(<span class="hljs-string">"users"</span>, <span class="hljs-string">"email"</span>, email);
        <span class="hljs-keyword">if</span> (userAlreadyExists) {
            res.status(<span class="hljs-number">400</span>).json(<span class="hljs-string">"Email must be unique"</span>);
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">await</span> insertRecord(<span class="hljs-string">"users"</span>, user);
            res.status(<span class="hljs-number">200</span>).json(<span class="hljs-string">"User created successfully"</span>);
        }
    } <span class="hljs-keyword">catch</span> (err) {
        res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">err</span>: err.message });
    }
};

<span class="hljs-built_in">module</span>.exports = { register };
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/SUccessValid-1.JPG" alt="Image" width="600" height="400" loading="lazy">
<em>API testing in Postman</em></p>
<p>Ensuring that the code followed our defined schema resulted in it being successfully executed.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>With this, we have come to the end of the tutorial. I hope you’ve learned about data validation, various data validation tools and data validation best practices.</p>
<p>You can also reach out to me and check out my other articles <a target="_blank" href="https://www.freecodecamp.org/news/author/oluwatobi/">here</a>. Till next time, keep on coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Extract an Error Object from a Blob API Response in JavaScript ]]>
                </title>
                <description>
                    <![CDATA[ I encountered an issue when I made a GET request in my React project which was supposed to return a file I could download. For the file to download properly, I had to make the response type a blob.  But if an error occurred when the server returns a ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-extract-an-error-object-from-a-blob/</link>
                <guid isPermaLink="false">66bb45031eb3452dfca5384e</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Olabisi Olaoye ]]>
                </dc:creator>
                <pubDate>Fri, 29 Mar 2024 00:34:05 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/03/React-form-validation--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>I encountered an issue when I made a GET request in my React project which was supposed to return a file I could download. For the file to download properly, I had to make the response type a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">blob</a>. </p>
<p>But if an error occurred when the server returns a JSON object, I'd be unable to get that object because I had already defined the response type as a blob. And if I remove the blob definition, the file would just return as regular JSON and might not download properly. </p>
<p>So how do I get the blob to download it and retrieve the error object in case something didn't go well from the server? Thankfully, there's a way to achieve this. </p>
<p>This guide will show you how to retain a JSON object for error handling purposes, while being able to download a file from a server. We'll be using <a target="_blank" href="https://axios-http.com/">Axios</a>, a JavaScript library used for making HTTP requests, to make our API call.</p>
<h2 id="heading-step-1-define-the-response-type-in-the-api-call">Step 1: Define the Response Type in the API Call</h2>
<p>First, define a function that makes the HTTP request to the server. In this case, we're expecting a file, so the conventional HTTP verb would be GET. </p>
<p>The response type for Axios requests is JSON by default, but we want to change that to a blob like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">"axios"</span>;

<span class="hljs-keyword">const</span> getFileFromServer = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">'https://api.some-server.com'</span>, {<span class="hljs-attr">responseType</span>: <span class="hljs-string">'blob'</span>})?.data;
    <span class="hljs-keyword">return</span> res;
}
</code></pre>
<h2 id="heading-step-2-convert-blob-to-text">Step 2: Convert Blob To Text</h2>
<p>In the previous step, we were able to get our file as a blob easily. But when it comes to showing the error, we need it to show as JSON. </p>
<p>First, we need to wrap the request in a <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch">try/catch</a> statement to specify what should happen if an error is thrown while the request is being made.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">"axios"</span>;

<span class="hljs-keyword">const</span> getFileFromServer = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">'https://api.some-server.com'</span>, {<span class="hljs-attr">responseType</span>: <span class="hljs-string">'blob'</span>}).data;
    <span class="hljs-keyword">return</span> res;
    }
    <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-keyword">let</span> errorResponse = <span class="hljs-keyword">await</span> error.response.data.text();
        <span class="hljs-keyword">const</span> errorObj = <span class="hljs-built_in">JSON</span>.parse(response);
        <span class="hljs-built_in">console</span>.log(errorObj) <span class="hljs-comment">// log error to console</span>
    }
}
</code></pre>
<p>The type conversion was done inside the <code>catch</code> block. First, we converted the response data to a JSON string using the <code>text()</code> method from JavaScript's <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch API</a>.</p>
<p>Finally, we used the <code>JSON.parse()</code> method to convert that string to actual JSON. That way, we can access the object in its intended format while being able to retrieve the file from the server if there is no error.</p>
<p>Logging the error object to the console will result in something like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"statusCode"</span>: <span class="hljs-number">400</span>,
  <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Some error occured"</span>
}
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This is one of the problems I faced in real life, so I thought I'd share it in case someone else encounters it. </p>
<p>Let me know your thoughts about the article, and feel free to make any suggestions you think could improve my solution.</p>
<p>Thanks for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Custom API Gateway with Node.js ]]>
                </title>
                <description>
                    <![CDATA[ In the era of microservices, where applications are divided into smaller, independently deployable services, managing and securing the communication between these services becomes crucial. This is where an API gateway comes into play.  An API gateway... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-custom-api-gateway-with-node-js/</link>
                <guid isPermaLink="false">66baee892c1f85b4545c8bf4</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Microservices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Iroro Chadere ]]>
                </dc:creator>
                <pubDate>Fri, 08 Mar 2024 23:16:38 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/03/Building-custom-API-gateway.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In the era of <a target="_blank" href="https://www.brightsidecodes.com/blog/understanding-microservices-and-api-gateway">microservices</a>, where applications are divided into smaller, independently deployable services, managing and securing the communication between these services becomes crucial. This is where an API gateway comes into play. </p>
<p>An API gateway serves as a central entry point for all client requests. It provides various functionalities such as routing, load balancing, authentication, and rate limiting.</p>
<p>In this article, we’ll explore how you can build out a custom API gateway using Node.js.</p>
<h3 id="heading-heres-what-well-cover">Here's what we'll cover:</h3>
<ol>
<li><a class="post-section-overview" href="#heading-what-is-an-api-gateway">What is an API Gateway?</a></li>
<li><a class="post-section-overview" href="#heading-security-in-api-gateways">Security in API Gateways</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-a-custom-api-gateway-with-nodejs">How to Build a Custom API Gateway with Node.js</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ol>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>This is a beginner's guide that should be relatively easy to follow. But to fully understand and get the most out of it, basic knowledge of <a target="_blank" href="https://nodejs.org/">Node.js</a> such as installation, setting up, and spinning up a server is vital. </p>
<p>Without further ado, let's dig in!</p>
<h2 id="heading-what-is-an-api-gateway">What is an API Gateway?</h2>
<p>API gateways act as intermediaries between clients and back-end services in a Microservices architecture. They abstract the complexity of the underlying services and expose a unified API to clients. </p>
<p>By consolidating multiple service endpoints into a single entry point, API gateways simplify client-side code and improve the overall scalability and performance of the system.</p>
<p>Compared to other popular API gateway solutions like Kong, AWS API Gateway, and Tyke, building a custom API gateway using Node.js offers flexibility and customization options tailored to your specific project requirements.</p>
<p>To get a little more understanding of what an API gateway is, I recommend you <a target="_blank" href="https://www.brightsidecodes.com/blog/understanding-microservices-and-api-gateway">check out this article</a> if you haven’t.</p>
<h3 id="heading-benefits-of-using-an-api-gateway">Benefits of Using an API Gateway:</h3>
<ul>
<li><strong>Improved scalability and performance through request routing and load balancing:</strong> API gateways facilitate request routing and load balancing, distributing incoming traffic across multiple backend services to ensure optimal performance and scalability.</li>
<li><strong>Simplified client-side code by providing a unified API endpoint</strong>: With a unified API endpoint provided by the API gateway, clients can interact with multiple services seamlessly, reducing complexity and improving the maintainability of client-side code.</li>
<li><strong>Enhanced Security</strong>: API gateways offer robust security features such as authentication, authorization, and rate limiting, protecting backend services from unauthorized access and potential security threats.</li>
</ul>
<h2 id="heading-security-in-api-gateways">Security in API Gateways</h2>
<p>Security is paramount in modern software development, especially when dealing with distributed systems and microservices. API gateways play a crucial role in enforcing security measures to safeguard sensitive data and prevent unauthorized access to APIs.</p>
<p>Common security features implemented in API gateways include:</p>
<ul>
<li>JWT Authentication: Verifying the identity of clients using JSON Web Tokens (JWT) to ensure secure communication between clients and backend services.</li>
<li>OAuth2 Integration: Providing secure access control and authorization mechanisms using OAuth2 protocols to authenticate and authorize client requests.</li>
<li>SSL Termination: Encrypting traffic between clients and the API gateway using SSL/TLS protocols to protect data in transit from eavesdropping and tampering.</li>
</ul>
<p>Now you should have a general overview of what an API gateway is and why it's important.</p>
<p>In the next section, we will delve into the process of building a custom API gateway using Node.js. I'll demonstrate how to implement security features using the http-proxy-middleware package.</p>
<h2 id="heading-how-to-build-a-custom-api-gateway-with-nodejs">How to Build a Custom API Gateway with Node.js</h2>
<p>As I've already discussed, we'll be using Node.js for this tutorial. In my opinion, Node.js is by far the easiest and most popular web framework. Anyone can learn how to use it.</p>
<p>For this guide, I assume you already know or have a basic understanding of Node.js and how to set up a server.</p>
<h3 id="heading-getting-started-installations-and-setup">Getting Started – Installations and Setup</h3>
<p>To get started, create a new folder called “API-gateway” entirely outside your front-end or your back-end code. Once the folder is created, open it on your terminal and run <code>npm init -y</code>. This will set up <code>npm</code> and then you’re ready to roll things out!</p>
<p>We’ll be using a couple of NPM packages, and it’s best to install them now. The most important one is the <code>http-proxy-middleware</code>. This middleware or package is what will route our requests from one endpoint (www.domain.com/auth ) to each corresponding endpoint (www.externaldomain.com/v1/bla/auth, www.externaldomain.com/v1/bla/projects ) as defined in our microservices.</p>
<p>To install the http-proxy-middleware, simply run <code>npm i http-proxy-middleware</code> on the root folder on your terminal. If it's installed, you’re good to go.</p>
<p>Next, we’ll need the remaining packages. Simply run <code>npm install express cors helmet morgan</code> on your terminal in the root folder of the API gateway. </p>
<p>The above command installs the following:</p>
<ul>
<li><strong>Express</strong>: our Node.js library for creating our server and running our code</li>
<li><strong>Cors</strong>: middleware to manage and control any cross-origin requests</li>
<li><strong>Helmet</strong>: yet another middleware for securing our HTTP response headers</li>
<li><strong>Morgan</strong>: a logging tool we can use to track both success and error logs</li>
</ul>
<p>Lastly, install Nodemon. This is a tool that spins up your server whenever you save a file using <code>npm install --save-dev nodemon</code>.</p>
<p>Now, go to your package.js file and update the scripts section. It should look like this:</p>
<pre><code><span class="hljs-string">"scripts"</span>: {
 <span class="hljs-string">"start"</span>: <span class="hljs-string">"node index.js"</span>,
 <span class="hljs-string">"dev"</span>: <span class="hljs-string">"nodemon index.js"</span>,
 <span class="hljs-string">"test"</span>: <span class="hljs-string">"echo \"Error: no test specified\" &amp;&amp; exit 1"</span>
},
</code></pre><p>To finally start testing things out, create a new file called index.js in that same api-gateway folder.  </p>
<p>If you get everything right, you should have the following files:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Screenshot-from-2024-03-04-12-50-56.png" alt="Image" width="600" height="400" loading="lazy">
<em>An image showing the file structure of our code base</em></p>
<h3 id="heading-putting-it-all-together">Putting it All Together</h3>
<p>A good code practice is to break things down as much as possible into smaller components. </p>
<p>But for this guide, we’re going to break that rule and put all the code into that one <code>index.js</code> file we created from the steps above. We'll be doing it this way because having too many files and an overly complex set up here might be confusing, especially while you're learning how things work.</p>
<p>First thing first, open the index.js file you’ve created and paste the following code into it:</p>
<pre><code><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cors"</span>);
<span class="hljs-keyword">const</span> helmet = <span class="hljs-built_in">require</span>(<span class="hljs-string">"helmet"</span>);
<span class="hljs-keyword">const</span> morgan = <span class="hljs-built_in">require</span>(<span class="hljs-string">"morgan"</span>);
<span class="hljs-keyword">const</span> { createProxyMiddleware } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"http-proxy-middleware"</span>);
</code></pre><p>In the code above we're just importing packages. </p>
<p>Next up, initialize and set up the imported packages like this:</p>
<pre><code><span class="hljs-comment">// Create an instance of Express app</span>
<span class="hljs-keyword">const</span> app = express();


<span class="hljs-comment">// Middleware setup</span>
app.use(cors()); <span class="hljs-comment">// Enable CORS</span>
app.use(helmet()); <span class="hljs-comment">// Add security headers</span>
app.use(morgan(<span class="hljs-string">"combined"</span>)); <span class="hljs-comment">// Log HTTP requests</span>
app.disable(<span class="hljs-string">"x-powered-by"</span>); <span class="hljs-comment">// Hide Express server information</span>
</code></pre><p>Remember that an API gateway is a single source of truth for all your services or external URLs. This means you must have other services or URLs you want to forward the requests to. </p>
<p>Assuming you already have your other services running either locally or deployed, let’s move to the next section of the code.</p>
<pre><code><span class="hljs-comment">// Define routes and corresponding microservices</span>
<span class="hljs-keyword">const</span> services = [
 {
   <span class="hljs-attr">route</span>: <span class="hljs-string">"/auth"</span>,
   <span class="hljs-attr">target</span>: <span class="hljs-string">"https://your-deployed-service.herokuapp.com/auth"</span>,
 },
 {
   <span class="hljs-attr">route</span>: <span class="hljs-string">"/users"</span>,
   <span class="hljs-attr">target</span>: <span class="hljs-string">"https://your-deployed-service.herokuapp.com/users/"</span>,
 },
 {
   <span class="hljs-attr">route</span>: <span class="hljs-string">"/chats"</span>,
   <span class="hljs-attr">target</span>: <span class="hljs-string">"https://your-deployed-service.herokuapp.com/chats/"</span>,
 },
 {
   <span class="hljs-attr">route</span>: <span class="hljs-string">"/payment"</span>,
   <span class="hljs-attr">target</span>: <span class="hljs-string">"https://your-deployed-service.herokuapp.com/payment/"</span>,
 },
 <span class="hljs-comment">// Add more services as needed either deployed or locally.</span>
];
</code></pre><p>In the above code, we created a services array list and defined objects each containing routes (where we’ll make requests to) and targets (where the requests will be forwarded to).  </p>
<p>Make sure to update the routes and targets to suit your needs.</p>
<p>Can you guess what’s next…?</p>
<p>Well, it’s finally time to create the simple logic to forward the requests to our target URL, setting up a rate limit and timeouts. And do you know what’s coming next? A code sample, lol:</p>
<pre><code><span class="hljs-comment">// Define rate limit constants</span>
<span class="hljs-keyword">const</span> rateLimit = <span class="hljs-number">20</span>; <span class="hljs-comment">// Max requests per minute</span>
<span class="hljs-keyword">const</span> interval = <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>; <span class="hljs-comment">// Time window in milliseconds (1 minute)</span>

<span class="hljs-comment">// Object to store request counts for each IP address</span>
<span class="hljs-keyword">const</span> requestCounts = {};

<span class="hljs-comment">// Reset request count for each IP address every 'interval' milliseconds</span>
<span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">Object</span>.keys(requestCounts).forEach(<span class="hljs-function">(<span class="hljs-params">ip</span>) =&gt;</span> {
    requestCounts[ip] = <span class="hljs-number">0</span>; <span class="hljs-comment">// Reset request count for each IP address</span>
  });
}, interval);

<span class="hljs-comment">// Middleware function for rate limiting and timeout handling</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rateLimitAndTimeout</span>(<span class="hljs-params">req, res, next</span>) </span>{
  <span class="hljs-keyword">const</span> ip = req.ip; <span class="hljs-comment">// Get client IP address</span>

  <span class="hljs-comment">// Update request count for the current IP</span>
  requestCounts[ip] = (requestCounts[ip] || <span class="hljs-number">0</span>) + <span class="hljs-number">1</span>;

  <span class="hljs-comment">// Check if request count exceeds the rate limit</span>
  <span class="hljs-keyword">if</span> (requestCounts[ip] &gt; rateLimit) {
    <span class="hljs-comment">// Respond with a 429 Too Many Requests status code</span>
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">429</span>).json({
      <span class="hljs-attr">code</span>: <span class="hljs-number">429</span>,
      <span class="hljs-attr">status</span>: <span class="hljs-string">"Error"</span>,
      <span class="hljs-attr">message</span>: <span class="hljs-string">"Rate limit exceeded."</span>,
      <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span>,
    });
  }

  <span class="hljs-comment">// Set timeout for each request (example: 10 seconds)</span>
  req.setTimeout(<span class="hljs-number">15000</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Handle timeout error</span>
    res.status(<span class="hljs-number">504</span>).json({
      <span class="hljs-attr">code</span>: <span class="hljs-number">504</span>,
      <span class="hljs-attr">status</span>: <span class="hljs-string">"Error"</span>,
      <span class="hljs-attr">message</span>: <span class="hljs-string">"Gateway timeout."</span>,
      <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span>,
    });
    req.abort(); <span class="hljs-comment">// Abort the request</span>
  });

  next(); <span class="hljs-comment">// Continue to the next middleware</span>
}

<span class="hljs-comment">// Apply the rate limit and timeout middleware to the proxy</span>
app.use(rateLimitAndTimeout);

<span class="hljs-comment">// Set up proxy middleware for each microservice</span>
services.forEach(<span class="hljs-function">(<span class="hljs-params">{ route, target }</span>) =&gt;</span> {
  <span class="hljs-comment">// Proxy options</span>
  <span class="hljs-keyword">const</span> proxyOptions = {
    target,
    <span class="hljs-attr">changeOrigin</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">pathRewrite</span>: {
      [<span class="hljs-string">`^<span class="hljs-subst">${route}</span>`</span>]: <span class="hljs-string">""</span>,
    },
  };

  <span class="hljs-comment">// Apply rate limiting and timeout middleware before proxying</span>
  app.use(route, rateLimitAndTimeout, createProxyMiddleware(proxyOptions));
});
</code></pre><p>I added a bunch of good code comments to help you understand what's going on.</p>
<p>Congratulations if you know what’s happening above. If you don’t, you can read about the <a target="_blank" href="https://www.npmjs.com/package/http-proxy-middleware">http-proxy-middleware</a> package.</p>
<p>But let’s get serious, we’re not done yet. </p>
<p>The above code still won’t work, as we need one more thing: writing a function to start the server when called upon.</p>
<p>Add the following code sample to the bottom of the index.js after all of the code you’ve added above:</p>
<pre><code><span class="hljs-comment">// Define port for Express server</span>
<span class="hljs-keyword">const</span> PORT = process.env.PORT || <span class="hljs-number">5000</span>;


<span class="hljs-comment">// Start Express server</span>
app.listen(PORT, <span class="hljs-function">() =&gt;</span> {
 <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Gateway is running on port <span class="hljs-subst">${PORT}</span>`</span>);
});
</code></pre><p>With that, when you run <code>npm run dev</code>, it spins up your server and you should be able to test this out using tools like Postman or any other tool you use to test APIs.</p>
<p>Now, before we go, let’s try to make this a little bit spicy! </p>
<p>Let’s add a 404 function to track and return a nice 404 message to a user if they navigate or send a request to a URL that doesn’t exist. </p>
<p>So on our services array defined above, we don’t have any routes defined for <code>products</code>. This means that if a user sends a request to <code>/product</code>, they’d get a server error because the request can’t be handled. </p>
<p>To tell the user that the URL is not found, we can add the following code sample just before we define the port and listen to it:</p>
<pre><code><span class="hljs-comment">// Handler for route-not-found</span>
app.use(<span class="hljs-function">(<span class="hljs-params">_req, res</span>) =&gt;</span> {
 res.status(<span class="hljs-number">404</span>).json({
   <span class="hljs-attr">code</span>: <span class="hljs-number">404</span>,
   <span class="hljs-attr">status</span>: <span class="hljs-string">"Error"</span>,
   <span class="hljs-attr">message</span>: <span class="hljs-string">"Route not found."</span>,
   <span class="hljs-attr">data</span>: <span class="hljs-literal">null</span>,
 });
});


<span class="hljs-comment">// Define port for Express server</span>
</code></pre><h2 id="heading-conclusion">Conclusion</h2>
<p>Building a custom API gateway with Node.js offers developers a flexible and customizable solution for managing, routing, and securing API calls in a microservices architecture. </p>
<p>Throughout this tutorial, we've explored the fundamental concepts of API gateways, including their role in simplifying client-side code, improving scalability and performance, and enhancing security.</p>
<p>By leveraging the power of Node.js and the <code>http-proxy-middleware</code> package, we've demonstrated how to implement a basic API gateway that proxies requests to multiple backend services. We've also enhanced our gateway with essential features such as rate limiting and timeouts to ensure reliable and secure communication between clients and services.</p>
<p>As you continue to explore the world of microservices and distributed systems, remember that API gateways play a crucial role in orchestrating communication and enforcing security measures. Whether you choose to build a custom solution or utilize existing gateway platforms, understanding the principles and best practices outlined in this tutorial will empower you to architect robust and scalable systems.</p>
<p>I encourage you to experiment with the code samples provided and explore further customization options to suit your project's unique requirements. The complete source code for this tutorial can be found here: https://github.com/irorochad/api-gateway.</p>
<p>Thank you for joining me on this journey to explore the intricacies of API gateways with Node.js. Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create a CRUD API – NodeJS and Express Project for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ An API is a technology that powers communication between software applications on the internet. API stands for Application Programming Interface, and it is basically a set of rules and protocols that define how different software can interact with ea... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/create-crud-api-project/</link>
                <guid isPermaLink="false">66ba305cf1ac6be9964fe7ac</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ backend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ crud ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Victor Yakubu ]]>
                </dc:creator>
                <pubDate>Fri, 08 Mar 2024 11:04:53 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/03/Swimm-Images-4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>An API is a technology that powers communication between software applications on the internet. API stands for Application Programming Interface, and it is basically a set of rules and protocols that define how different software can interact with each other. </p>
<p>Imagine having two different programs: program A and program B. For these two programs to communicate together, an API is needed, and a set of rules ensure that they know what to expect when they interact with each other.</p>
<p>As a backend developer, your responsibilities involve building server-side applications, handling data storage, and providing the necessary functionalities to do all these through APIs. </p>
<p>There are <a target="_blank" href="https://blog.postman.com/different-types-of-apis/">different types of APIs</a> like REST, GraphQL, gRPC, SOAP, and WebSockets. However, when it comes to web development, one is more popular, and that is the <a target="_blank" href="https://www.freecodecamp.org/news/what-is-a-rest-api/">REST API</a>.</p>
<p>In this article, you will learn how to create a CRUD API with Node.js and Express using the REST architecture, and by the end of this article, you should have a fully functional API that is able to perform CRUD operations.</p>
<p>So, let's dive into the world of backend development with Node.js and Express and get started on our journey to building a CRUD API.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-what-is-a-crud-api">What is a CRUD API?</a></li>
<li><a class="post-section-overview" href="#heading-what-is-nodejs">What is Node.js?</a></li>
<li><a class="post-section-overview" href="#heading-why-node">Why Node?</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-nodejs">How to Install Node.js</a></li>
<li><a class="post-section-overview" href="#heading-what-is-express">What is Express?</a></li>
<li><a class="post-section-overview" href="#heading-why-do-you-need-express">Why do You Need Express?</a></li>
<li><a class="post-section-overview" href="#heading-prerequisites">Prerequisite<strong>s</strong></a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-your-development-environment">How to Set Up Your Development Environment</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-a-server-for-your-crud-restful-api-with-nodejs-and-express">How to Set up a server for Your CRUD Restful API with Node.js and Express</a></li>
<li><a class="post-section-overview" href="#heading-crud-api-example">CRUD API Example</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-api-routes">How to Create API Routes</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-your-own-crud-api">How to Create Your own CRUD API</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-the-get-users-endpoint">How to Create the GET /users Endpoint</a></li>
<li><a class="post-section-overview" href="#heading-how-to-test-your-api-get-request">How to Test Your API GET Request</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-the-post-users-endpoint">How to Create the POST /users Endpoint</a></li>
<li><a class="post-section-overview" href="#heading-how-to-test-the-post-request">How to Test the POST Request</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-the-get-usersid-endpoint">How to Create the GET /users/:id Endpoint</a></li>
<li><a class="post-section-overview" href="#heading-how-to-test-the-get-request">How to Test the GET Request</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-the-delete-usersid">How to Create the DELETE /users/:id</a></li>
<li><a class="post-section-overview" href="#heading-how-to-test-the-delete-request">How to Test the DELETE Request</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-the-patch-usersid-endpoint">How to Create the PATCH /users/:id Endpoint</a></li>
<li><a class="post-section-overview" href="#heading-how-to-test-the-patch-request">How to Test the PATCH Request</a></li>
<li><a class="post-section-overview" href="#heading-wrapping-up">Wrapping UP</a></li>
</ul>
<h2 id="heading-what-is-a-crud-api">What is a CRUD API?</h2>
<p>In web development, CRUD operations are the bread and butter of backend systems. This is because they allow you to "Create", "Read", "Update", and "Delete" data through your API. </p>
<p>Here's a quick overview of the four primary HTTP methods associated with CRUD operations:</p>
<ul>
<li><strong>GET</strong>: Used for reading or retrieving data from the server.</li>
<li><strong>POST</strong>: Used for creating new data on the server.</li>
<li><strong>PUT</strong>: Used for updating existing data on the server.</li>
<li><strong>DELETE</strong>: Used for removing data from the server.</li>
</ul>
<p>Virtually every web application interacts with a database to perform these four core operations. Whether it's a social media platform, an e-commerce website, or a weather app, they all rely on creating, reading, updating, and deleting data. </p>
<p>For example, WhatsApp recently added an edit feature to the application, allowing users to make corrections to an already-sent message. That's one part of the CRUD operation in action (updating).</p>
<p>In the context of building a web API, these operations become the backbone of how your application interacts with data. Your API provides endpoints that allow client applications (like web or mobile apps) to perform these operations on the server. </p>
<p>This communication between the client and the server is the essence of web development, and understanding how to create a CRUD API is a crucial skill for a web developer.</p>
<h2 id="heading-what-is-nodejs">What is Node.js?</h2>
<p>Node.js is an open-source and cross-platform runtime environment for executing JavaScript code outside of a browser. Quite often, we use it to build back-end services, also called APIs. Node is ideal for building highly scalable, data-intensive and real-time back-end services that power our client applications</p>
<h2 id="heading-why-node">Why Node?</h2>
<ul>
<li>It is one of the most popular choices for building the backend.</li>
<li>You get to write JavaScript across your entire stack, making it easier to transition from either a frontend developer to a backend developer and vice versa.</li>
<li>It allows for easy scaling of applications, making it a good choice for large-scale professional projects.</li>
<li>It is fast and non-blocking. This is because of the asynchronous event-driven nature of Node.js.</li>
<li>Node.js has a vibrant community and a rich ecosystem of packages and libraries.</li>
</ul>
<h2 id="heading-how-to-install-nodejs">How to Install Node.js</h2>
<p>Installation steps:</p>
<ol>
<li>Download the Mac/Windows installer from the <a target="_blank" href="https://nodejs.org/en/download">Node.js website</a>.</li>
<li>Choose the Long-Term Support (LTS) version that’s shown on the left</li>
<li>After downloading, install/run the installer, and then follow the prompts. (You will have to click the NEXT button a bunch of times and accept the default installation settings</li>
<li>To confirm that Node has been successfully installed, open your terminal and run the command. (For Windows, you might need to restart your command before running it.)</li>
</ol>
<pre><code>node –version
</code></pre><h2 id="heading-what-is-express">What is Express?</h2>
<p>Express is a fast, unopinionated, and minimalist web backend or server-side web framework for Node.js. Basically, it gives you the ability to build your APIs how you want them, with less code.</p>
<p>It is a framework built on top of Node.js that allows you to create your Backend with ease. You can use Express in combination with frontend frameworks like React, Angular, or Vue to build full-stack applications.</p>
<h2 id="heading-why-do-you-need-express">Why do You Need Express?</h2>
<ul>
<li>It makes building web applications with Node.js much easier.</li>
<li>It is extremely light, fast and free.</li>
<li>It is used for both server-rendered apps as well as API/Microservices.</li>
<li>It is the most popular Node.</li>
<li>It gives you full control over requests and responses.</li>
</ul>
<h2 id="heading-prerequisites">Prerequisite<strong>s</strong></h2>
<p>To follow along, you'll need to have the following:</p>
<ul>
<li>Basic knowledge of JavaScript</li>
<li>Download and install <a target="_blank" href="https://nodejs.org/en">Node.js</a> and <a target="_blank" href="https://www.postman.com/downloads/">Postman</a> on your computer</li>
</ul>
<p>See the complete code for this tutorial on <a target="_blank" href="https://github.com/Aviatorscode2/crud-api-tutorial">Github</a>.</p>
<h2 id="heading-how-to-set-up-your-development-environment">How to Set Up Your Development Environment</h2>
<p>Before diving into creating your API, let's go through the process of creating a basic server on your local computer. </p>
<p>Here are the steps to follow:</p>
<h3 id="heading-step-1-create-directory">Step #1 – Create Directory</h3>
<p>Create a directory/folder on your computer. Open the folder in a code editor</p>
<h3 id="heading-step-2-create-indexjs-file">Step #2 – Create index.js File</h3>
<p>Create an <strong>index.js</strong> file inside the folder using this command:</p>
<pre><code>touch index.js
</code></pre><h3 id="heading-step-3-initialize-npm">Step #3 – Initialize NPM</h3>
<p>Initialize NPM inside the folder by running this command in your terminal:</p>
<pre><code>npm init -y
</code></pre><p>The command will create a <strong>package.json</strong> file with default values.</p>
<h3 id="heading-step-5-install-express">Step #5 – Install Express</h3>
<p>Use the command below to install Express.js</p>
<pre><code>npm install express
</code></pre><p>After installing Express, go to the <strong>package.json</strong> file and include <strong>“type”: “module”</strong>. This declaration will tell Node that this project will be using <a target="_blank" href="https://nodejs.org/api/packages.html#type">ES6 module syntax</a> (import/export) instead of common.js, which is the default in Node.  </p>
<p><img src="https://lh7-us.googleusercontent.com/s0sHrHSK7ulqOU4Ddw6WzER6waGEGaCxV26mk-ieuqDVKYBZSKlJ1aDW8WXbCOlfzC1xcW8lXh1-HVSNnYUXYM3MRmmy0N-6uQh-J3qDnjHtxB5atRbJQ-cxR1AWzLTa9RTwv_cXNXpNGz3lbSBHejM" alt="Image" width="600" height="400" loading="lazy">
<em>package.json file with <code>type:module</code></em></p>
<h2 id="heading-how-to-set-up-a-server-for-your-crud-restful-api-with-nodejs-and-express">How to Set up a server for Your CRUD Restful API with Node.js and Express</h2>
<p>To create a CRUD <a target="_blank" href="https://www.freecodecamp.org/news/what-is-rest-rest-api-definition-for-beginners/#:~:text=An%20API%20that%20complies%20with%20some%20or%20all%20of%20the%20six%20guiding%20constraints%20of%20REST%20is%20considered%20to%20be%20RESTful.">restful</a> API, you first need to set up your server. You can do this by following these steps:</p>
<h3 id="heading-step-1-write-your-server-application-code-inside-the-indexjs-file">Step #1 – Write your Server Application Code inside the index.js file</h3>
<p>Basically, a server code will look like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> bodyParser <span class="hljs-keyword">from</span> ‘body-parser

<span class="hljs-keyword">const</span> app = express();
<span class="hljs-keyword">const</span> PORT = <span class="hljs-number">5000</span>

app.use(bodyParser.json());

app.listen(PORT, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server running on port: http://localhost:<span class="hljs-subst">${PORT}</span>`</span>));
</code></pre>
<p>Here's an explanation for the code above:</p>
<ul>
<li>In the first line, we imported <code>express</code> from the Express module that we installed.</li>
<li>The <code>bodyParser</code> comes with Express, and it allows us to take in the incoming POST request body.</li>
<li>Next, we created an <code>app</code> using the <code>express</code> object.</li>
<li>We then specified the port for the application – it was set to <strong>5000</strong> (if you get an error using this port, it might be that the port is currently being used by a different app, so you can either change your port or stop the other app from using the port).</li>
<li>Next, we specified that JSON data will be used in the application.</li>
<li>Once that was created, we used the <code>listen</code> method on the <code>app</code> to make our application listen for incoming requests. The method accepts two things: the <strong>PORT</strong>, which is where we would be listening for requests from our client side, and a callback function that will be triggered when our server is set up.</li>
</ul>
<h3 id="heading-step-2-start-the-server">Step #2 – Start the Server</h3>
<p>Now you can start your server by running this command. If you used a different file, replace index.js with the file name where the server is located.</p>
<pre><code>node index.js
</code></pre><p>Now your server should be running on <strong>port 5000</strong>. You can verify that your server is running on your terminal</p>
<h3 id="heading-step-3-install-nodemon-optional">Step #3 – Install Nodemon (Optional)</h3>
<p>At the moment, anytime you make changes to your server file, you will need to restart the server before your changes can reflect (you can try it and see). So to take care of this challenge, you can use Nodemon. Run the command to install it:</p>
<pre><code>npm install nodemon
</code></pre><p>To use Nodemon, head over to your <strong>package.json</strong> file and set up a script. Replace your start script with this instead:</p>
<pre><code><span class="hljs-string">"start"</span>: <span class="hljs-string">"nodemon index.js"</span>
</code></pre><p>Note that <strong>index.js</strong> is the file where the server code is written.</p>
<p>Now you can start your server by running this command:</p>
<pre><code>npm start
</code></pre><p>With this code, we've set up a server that listens on port 5000 and prints a message when it starts. But this is just the beginning because our server needs to do much more. </p>
<p>Let's explore how to handle API requests next.</p>
<h2 id="heading-crud-api-example">CRUD API Example</h2>
<p>Let's start by defining the API routes for each CRUD operation. These routes will serve as the entry points for your API, and they will map to specific actions we want to perform on our data. </p>
<p>In our case, these actions are creating, reading, updating, and deleting data.</p>
<h2 id="heading-how-to-create-api-routes">How to Create API Routes</h2>
<p>When the port (<a target="_blank" href="http://localhost:5000/">http://localhost:5000/</a>) is opened in a browser, you will get an error.</p>
<p><img src="https://lh7-us.googleusercontent.com/p-3qnXzvHDxcsbhpYXO4VMopivQMhqgSPwSTXXDQJW_2GRhFFvkpL8JitNShGcrjyQiMx87ZwkK_4iEbs3JieTdRJQ13Q94O0hV7U9mwOpcm9ET7Yngb2TXQpItUrpepYzeXzg4rZMGdnxonhMREHAo" alt="Image" width="600" height="400" loading="lazy">
<em>localhost:5000 in the browser without any routes</em></p>
<p>This is because Node.js and Express are all about routing, and we don't any routes yet. </p>
<p>You can define API routes using the <code>app.get()</code>, <code>app.post()</code>, <code>app.put()</code>, and <code>app.delete()</code> methods in your Express application (in the <strong>index.js</strong> file). </p>
<p>Here's how to create a route to handle GET requests using the <code>app.get()</code> function:</p>
<pre><code>app.get(<span class="hljs-string">'/'</span>, (req, res)
</code></pre><p>The <code>app.get()</code> function accepts two parameters. The first is used to specify the path (in this case, it is '/'). </p>
<p>The next parameter is a callback function where you define what happens when the GET request is called. The function also has <a target="_blank" href="https://www.freecodecamp.org/news/express-explained-with-examples-installation-routing-middleware-and-more/">two parameters</a>: the request body (<code>req</code>), which can contain information such as the request query string, parameters, body, and HTTP headers. While the response object (<code>res</code>) contains the information you want to send </p>
<p>Here is the complete code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> bodyParser <span class="hljs-keyword">from</span> <span class="hljs-string">'body-parser'</span>
<span class="hljs-keyword">const</span> app = express();

<span class="hljs-keyword">const</span> PORT = <span class="hljs-number">5000</span>;

app.use(bodyParser.json());

app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'[GET ROUTE]'</span>);
    res.send(<span class="hljs-string">'HELLO FROM HOMEPAGE'</span>);
})

app.listen(PORT, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server running on port: http://localhost:<span class="hljs-subst">${PORT}</span>`</span>));
</code></pre>
<p>When you head back to <a target="_blank" href="http://localhost:5000/">http://localhost:5000/</a> and refresh it, you shouldn’t get an error anymore.</p>
<p><img src="https://lh7-us.googleusercontent.com/6289p5Vl_k4v2ULLd5iUVCpr-sykpbcEytcHsqoJZTB4MgWgDEfVuH1_4aTt0w9ThAhFNCn_rMrlwNFB2hfgOKWQ3znH3MHnHRxh0sd1czb_ntBHMIWS6HtAIo3yuCgDEFK7RVFGvTel0s89T3qTKjc" alt="Image" width="600" height="400" loading="lazy">
<em>localhost:5000 in the browser with GET route</em></p>
<h2 id="heading-how-to-create-your-own-crud-api">How to Create Your own CRUD API</h2>
<p>For this API, you will be handling a set of users. Handling users in a database is a great example because it is a common use case in most applications. </p>
<p>Here are the API endpoints you will be creating:</p>
<ol>
<li>GET /users - find all users</li>
<li>POST /users - creates a user</li>
<li>GET /users/:id - finds a specific user</li>
<li>DELETE /users/:id - deletes a specific user</li>
<li>PATCH /users/:id - updates a specific user.</li>
</ol>
<h3 id="heading-how-to-create-the-get-users-endpoint">How to Create the GET /users Endpoint</h3>
<p>Reading data is one of the most common operations in an API. In this example, you will be getting the list of all the users in your mock database. This information will be presented in JSON format.</p>
<p>To define a route to retrieve users' data from the database, follow these steps:</p>
<ul>
<li>Create a new folder called routes</li>
<li>Create a new file called <strong>users.j</strong>s inside the routes folder.</li>
<li>Write the code to set up the GET router.</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">const</span> router = express.Router();

<span class="hljs-comment">// Mock database</span>
<span class="hljs-keyword">const</span> users = [
  {
    <span class="hljs-attr">first_name</span>: <span class="hljs-string">'John'</span>,
    <span class="hljs-attr">last_name</span>: <span class="hljs-string">'Doe'</span>,
    <span class="hljs-attr">email</span>: <span class="hljs-string">'johndoe@example.com'</span>,
  },
  {
    <span class="hljs-attr">first_name</span>: <span class="hljs-string">'Alice'</span>,
    <span class="hljs-attr">last_name</span>: <span class="hljs-string">'Smith'</span>,
    <span class="hljs-attr">email</span>: <span class="hljs-string">'alicesmith@example.com'</span>,
  },
];

<span class="hljs-comment">// Getting the list of users from the mock database</span>
router.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    res.send(users);
})

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> router
</code></pre>
<p>In this code snippet:</p>
<ul>
<li><code>import express from 'express';</code> imports the Express.js framework</li>
<li><code>const router = express.Router();</code> creates a fresh router instance, stored in the variable router.</li>
<li>The <code>users</code> variable serves as the mock database containing an array of users.</li>
<li>The <code>router.get()</code> function sets up a route that responds to HTTP GET requests.</li>
<li>The second part of the code <code>(req, res) =&gt; { ... }</code> is a callback function. It gets executed when a request is made to the GET route.</li>
<li>Inside the callback function, we used <code>res.send(users)</code> to send a response back to the client. In this example, we're sending the <code>users</code> variable as the response. So when a user hits the GET URL, the server responds by sending the data inside the <code>users</code> variable in JSON format to the client.</li>
</ul>
<p>Save your changes in the <strong>users.js</strong> file. Then do the following in the <strong>index.js</strong> file:</p>
<p>import your user routes from <strong>user.js</strong>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> userRoutes <span class="hljs-keyword">from</span> <span class="hljs-string">'./routes/users.js'</span>
</code></pre>
<p>Use the <code>app.use</code> method, and specify the path and router handler:</p>
<pre><code class="lang-javascript">app.use(<span class="hljs-string">'/users'</span>, userRoutes);
</code></pre>
<p>When a user visits <a target="_blank" href="http://localhost:5000/users">http://localhost:5000/users</a>, the router is triggered. It effectively acts as a filter, determining when a specific set of routes should be applied.</p>
<p>Here is the complete code for the <strong>index.js</strong> file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> bodyParser <span class="hljs-keyword">from</span> <span class="hljs-string">'body-parser'</span>
<span class="hljs-keyword">const</span> app = express();
<span class="hljs-keyword">import</span> userRoutes <span class="hljs-keyword">from</span> <span class="hljs-string">'./routes/users.js'</span>

<span class="hljs-keyword">const</span> PORT = <span class="hljs-number">5000</span>;

app.use(bodyParser.json());

app.use(<span class="hljs-string">'/users'</span>, userRoutes);

app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> res.send(<span class="hljs-string">'HELLO FROM HOMEPAGE'</span>))

app.get(<span class="hljs-string">'/'</span>, (req, res));

app.listen(PORT, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server running on port: http://localhost:<span class="hljs-subst">${PORT}</span>`</span>));
</code></pre>
<h4 id="heading-how-to-test-your-api-get-request"><strong>How to Test Your API GET Request</strong></h4>
<p>You can use either a browser (browsers can only be used to perform GET requests) or Postman to test the GET request. </p>
<p>So copy your API URL, <a target="_blank" href="http://localhost:5000/users">http://localhost:5000/users</a>, and paste it either on Postman or in your browser. If you are using Postman, you will first need to make a GET request, then paste your API URL, and then click on send. After that, you will see the list of users in your Postman console.</p>
<p><img src="https://lh7-us.googleusercontent.com/XBhjewUFTBvBhc2larsYwqzS3-RHp7qFBrO4lvScf91EUFO5TEgt83iU48h9ArDK3EbrPwdS7_-WkI51JkDUHH4v2U9pWXdYMSbKeCpQrYRunvhuvAIAadzcAyj8y0hojjxs1CIlAo7TigoTJrM3y5c" alt="Image" width="600" height="400" loading="lazy">
<em>postman GET route test</em></p>
<h3 id="heading-how-to-create-the-post-users-endpoint">How to Create the POST /users Endpoint</h3>
<p>You can use the POST request to add data to your database. It accepts input from the client and stores it in the database. To create data in our API, we'll define a route that accepts POST requests and saves the data to the mock database you have set up. </p>
<p>But before that, you'll need the UUID package. Use this command to install it:</p>
<pre><code>npm install uuid
</code></pre><p>This package will help generate a unique ID for each user you will be creating. This will be useful when you are implementing the GET, DELETE, and PATCH user by ID requests, where you will need a way to identify a specific user.</p>
<p>So in the <strong>users.js</strong> file, do the following:</p>
<p>Import the <code>uuid</code> package:</p>
<pre><code><span class="hljs-keyword">import</span> { v4 <span class="hljs-keyword">as</span> uuidv4 } <span class="hljs-keyword">from</span> <span class="hljs-string">'uuid'</span>;
</code></pre><p>Secondly, you'll have to implement the code for the POST request. </p>
<p>Here is what it looks like:</p>
<pre><code class="lang-javascript">router.post(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> user = req.body;

    users.push({ ...user, <span class="hljs-attr">id</span>: uuidv4() });

    res.send(<span class="hljs-string">`<span class="hljs-subst">${user.first_name}</span> has been added to the Database`</span>);
})
</code></pre>
<p>In the code snippet:</p>
<ul>
<li>The <code>router.post()</code> function sets up a route that responds to HTTP POST requests. This means that when a client sends a POST request to the root URL of your application, this route will be triggered.</li>
<li>Within the callback function <code>(req, res) =&gt; { ... }</code>, we access the <code>req</code> object, which represents the incoming request made by the client. Specifically, we're interested in the <code>req.body</code> property. This property contains the data (first name, last name, and email) that the client will send as part of the POST request's body.</li>
<li>With <code>const user = req.body</code> we extract this data from <code>req.body</code> and store it in the <code>user</code> variable.</li>
<li>Next, we added the <code>user</code> data to an array called <code>users</code>. To ensure each user has a unique identifier, we generate a universally unique identifier (UUID) using a function like <code>uuidv4()</code> and include it as id in the user object. This helps in keeping user records distinct and identifiable.</li>
<li>Finally, we used <code>res.send()</code> to send a response back to the client. In this case, we're sending a simple message notifying the client that the user has been successfully added to the database. The message includes the user's first name for a personalized touch.</li>
</ul>
<p>Here is the complete code</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">'express'</span>;
<span class="hljs-keyword">import</span> { v4 <span class="hljs-keyword">as</span> uuidv4 } <span class="hljs-keyword">from</span> <span class="hljs-string">'uuid'</span>;

<span class="hljs-keyword">const</span> router = express.Router();

<span class="hljs-keyword">const</span> users = [];

<span class="hljs-comment">// Adding users to our mock database</span>

router.post(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> user = req.body;

    users.push({ ...user, <span class="hljs-attr">id</span>: uuidv4() });

    res.send(<span class="hljs-string">`<span class="hljs-subst">${user.first_name}</span> has been added to the Database`</span>);
})  

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> router
</code></pre>
<h4 id="heading-how-to-test-the-post-request"><strong>How to Test the POST Request</strong></h4>
<p>Here are the steps to follow to make a POST request on Postman:</p>
<ul>
<li>Go to Postman</li>
<li>Open a new request tab</li>
<li>Select "POST" from the list of available HTTP methods</li>
<li>In the URL field, enter the full URL where you want to send the POST request (<a target="_blank" href="http://localhost:5000/users">http://localhost:5000/users</a>)</li>
<li>Click on the "Body" tab in the request window.</li>
<li>Choose the format in which you want to send your POST data (choose JSON).</li>
<li>Enter the data you want to send in the request body. This data should match the format expected by the server.</li>
<li>Finally, click on “Send”  </li>
</ul>
<p><img src="https://lh7-us.googleusercontent.com/olNqMdggCcvhUotN9ospAjg8r7t-HE_3yUqGrEkf_dKnCGTwFiKWQJB3x4dccUbHPlYbP-j8S7a2xBG3TpMvceVdmz_nC8zRtaRQzfeknyyo-OQJDwuSFZJkWPiQctDkoTYfZCsVnE1ljQN96Hxow54" alt="Image" width="600" height="400" loading="lazy">
<em>postman POST route test</em></p>
<p>If it is successful, you will get a response saying, “Daanny has been added to the database."</p>
<p>To confirm if it has been added, make a GET request, and you should see the new user added to your database. (Note: This user information will be lost when your server restarts since it’s not saved to an actual database).</p>
<p><img src="https://lh7-us.googleusercontent.com/cHmJ3hcsMb5Vnj9zh_kSdqBMRrgtR1pQJ3-_DgHgukwdtyLrnG5AN0jyL4gGtzyTYUJj4w_ENO6ZqVsMZLqdnRytOgP4tR5A2B2T_TnDHNbRDEb9JAGF7SmTwVVwBczzg02JmLfWAucyeyMlnPs1Ehg" alt="Image" width="600" height="400" loading="lazy">
<em>postman GET route test</em></p>
<h3 id="heading-how-to-create-the-get-usersid-endpoint">How to Create the GET /users/:id Endpoint</h3>
<p>Fetching specific data based on a unique identifier, such as a user ID, is a common operation. It's often essential for building features like user profiles or retrieving individual records from a database. </p>
<p>In this section, you’ll explore how to use this endpoint (users/:id) to retrieve user information based on a provided user ID. </p>
<p>Let's get started!</p>
<pre><code class="lang-javascript">router.get(<span class="hljs-string">'/:id'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { id } = req.params;

    <span class="hljs-keyword">const</span> foundUser = users.find(<span class="hljs-function">(<span class="hljs-params">user</span>) =&gt;</span> user.id === id)

    res.send(foundUser)
});
</code></pre>
<p>Here's what this code snippet does:</p>
<ul>
<li>The <code>router.get()</code> function sets up a route that responds to HTTP GET requests. In this example, we've defined a route with ('/:id'). The :id part is a route parameter, which allows us to capture a dynamic value from the URL. In this case, it represents the user ID we want to retrieve.</li>
<li>Within the callback function <code>(req, res) =&gt; { ... }</code>, we can access the <code>req</code> object, which represents the incoming request made by the client. Specifically, we're interested in <code>req.params</code>, which holds the values of route parameters. In this case, we destructure <code>id</code> from <code>req.params</code>, effectively extracting the user ID from the URL. For example, if a client makes a GET request to /users/123, the id will be '123'.</li>
<li>We used the <code>.find()</code> method to search through this data based on the user ID (id) captured from the URL. This method attempts to find a user whose ID matches the provided id.</li>
<li>Once we located the user data (if it exists), we send it as the response using <code>res.send(foundUser)</code>. The <code>foundUser</code> variable contains the user object that matches the requested ID.</li>
</ul>
<h4 id="heading-how-to-test-the-get-request"><strong>How to Test the GET Request</strong></h4>
<p>In testing the API, follow these steps:</p>
<ul>
<li>Go to your POST request tab and make as many requests as you want to add a new user to the database.</li>
<li>Go to your GET request tab and make a request to see the list of users you have added</li>
</ul>
<p><img src="https://lh7-us.googleusercontent.com/qEuvm-1QKHvGmFCmru5dF2b9JSW5ua_dMcovZaKFZL_NChfSQqaazX4QvvSrSkjMzTO9Yqon5zhsZ5ZZwrWO0WifthlOKfboojC6ws8Fju2HK5TcE1oLvZZhOaO5xljRIUvYt_oun_NsIH13sq2Yf0o" alt="Image" width="600" height="400" loading="lazy">
<em>postman GET route test</em></p>
<ul>
<li>Copy the id of any of the users on the list</li>
<li>Create a new GET request tab, copy in the base API URL, and add the id of any of the users to it. It should be in a format like this: <a target="_blank" href="http://localhost:5000/users/734a9e75-b3f5-415f-82fb-79b4fdf1a593">http://localhost:5000/users/734a9e75-b3f5-415f-82fb-79b4fdf1a593</a></li>
<li>Click on “Send”. If it’s successful, you will see the user information of the user id you used for the request</li>
</ul>
<p><img src="https://lh7-us.googleusercontent.com/ui9cj6HDXIifMjzlnICvnzgIGOssvRbGeCrlos2DXG541b4r9Dsn0mVrXZBfyCwvO6gh3yovrbsDD1I7EU0-5j7r3fRIrQ4XFX1QR-vlFJlXuXb1Ht753re7ZDf2cC7VyPyLgee6a6gZmwjnkCb8RI0" alt="Image" width="600" height="400" loading="lazy">
<em>postman GET route test</em></p>
<h3 id="heading-how-to-create-the-delete-usersid">How to Create the DELETE /users/:id</h3>
<p>Sometimes, it's necessary to remove user accounts or specific records from a database for various reasons, such as account deactivation. </p>
<p>Here, you will explore how to use this endpoint to delete user data based on a provided user ID.</p>
<p>To delete data, we'll define a route that accepts DELETE requests and removes the data from the database.</p>
<p>See the code to delete a user from a database below</p>
<pre><code class="lang-javascript">router.delete(<span class="hljs-string">'/:id'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { id } = req.params;

  users = users.filter(<span class="hljs-function">(<span class="hljs-params">user</span>) =&gt;</span> user.id !== id)

  res.send(<span class="hljs-string">`<span class="hljs-subst">${id}</span> deleted successfully from database`</span>);
});
</code></pre>
<p>Here's what this code does:</p>
<ul>
<li>The <code>router.delete()</code> function sets up a route that responds to HTTP DELETE requests. In this example, we've defined a route with ('/:id'), where :id is a route parameter. It captures a dynamic value from the URL, representing the user ID we want to delete.</li>
<li>In the callback function <code>(req, res) =&gt; { ... }</code>, we can access the <code>req</code> object, which represents the incoming request made by the client. Specifically, we're interested in <code>req.params</code>, which holds the values of route parameters. Here, we destructure id from <code>req.params</code>, extracting the user ID from the URL. For instance, if a client sends a DELETE request to /users/123, id will be '123'.</li>
<li>Assuming that we have an array or database (users) containing user data, we employ the <code>.filter()</code> method to create a new array that excludes the user with the matching ID (id). This effectively removes the user from the data store.</li>
<li>After successfully deleting the user, we send a response back to the client using <code>res.send()</code>. The response contains a message confirming the deletion, including the user's ID that was deleted from the database.</li>
</ul>
<h4 id="heading-how-to-test-the-delete-request"><strong>How to Test the DELETE Request</strong></h4>
<p>Here are the steps to delete a user from the database on Postman:</p>
<ul>
<li>Go to Postman</li>
<li>Open a new request tab</li>
<li>Select "DELETE" from the list of available HTTP methods</li>
<li>Enter the URL. It should contain the id of the user you want to delete (for example: <a target="_blank" href="http://localhost:5000/users/734a9e75-b3f5-415f-82fb-79b4fdf1a593">http://localhost:5000/users/734a9e75-b3f5-415f-82fb-79b4fdf1a593</a>). If you don’t have a user in your database, you will need to create one and copy the id.</li>
<li>Click on “Send”.  </li>
</ul>
<p><img src="https://lh7-us.googleusercontent.com/bTO6dcTaujyxm7ruPcyCJTzMfZemFop1oqU803Fif2kCeq1Z2q3uB1-JHB1aK4-ePIimPvb1LBYuty8_YFd7xP_i8UVhFZewWHlmwKy3MlWssMWtdaBnXtCkyB8NZebDnldJzRXc_v0Fs7MS4rZJq_g" alt="Image" width="600" height="400" loading="lazy">
<em>postman DELETE route test</em></p>
<p><img src="https://lh7-us.googleusercontent.com/B9YHqAtEyg1f0O0CAojNJDXjK212LCxFodc9Ld3j04bdZsK9R5qHExSxaKGCAqgI0W79jHJ2bN3l9GkVgB3DYAd192zB9LzThvcAZJFuXoBO5JUqcUKWyBWcc6q4KirQA4B6Hi6t1NN3eU8e-dH10pY" alt="Image" width="600" height="400" loading="lazy">
<em>postman DELETE route test</em></p>
<h3 id="heading-how-to-create-the-patch-usersid-endpoint">How to Create the PATCH /users/:id Endpoint</h3>
<p>There are times when you don't need to update an entire resource or object. Instead, you'd want to make partial modifications or adjustments. This is where the HTTP PATCH request comes into play.</p>
<p>For example, after creating a new user, you can change either the first name, last name, or email of that user using the PATCH request. Let’s see how to do that.</p>
<pre><code class="lang-javascript">router.patch(<span class="hljs-string">'/:id'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { id } = req.params;

  <span class="hljs-keyword">const</span> { first_name, last_name, email} = req.body;

  <span class="hljs-keyword">const</span> user = users.find(<span class="hljs-function">(<span class="hljs-params">user</span>) =&gt;</span> user.id === id)

  <span class="hljs-keyword">if</span>(first_name) user.first_name = first_name;
  <span class="hljs-keyword">if</span>(last_name) user.last_name = last_name;
  <span class="hljs-keyword">if</span>(email) user.email = email;

  res.send(<span class="hljs-string">`User with the <span class="hljs-subst">${id}</span> has been updated`</span>)

});
</code></pre>
<p>Here's what this code snippet accomplishes:</p>
<ul>
<li>The <code>router.patch()</code> function sets up a route that responds to HTTP PATCH requests. In this example, we've defined a route with ('/:id'), where :id is a route parameter. It captures the dynamic value from the URL, representing the user ID we want to update.</li>
<li>Within the callback function <code>(req, res) =&gt; { ... }</code>, we can access the <code>req</code> object, which represents the incoming request made by the client. Specifically, we're interested in <code>req.params</code>, which hold the values of route parameters (id in this case), and <code>req.body</code>, which contains the data to be updated.</li>
<li>Next, we used <code>.find()</code> to locate the user object with the matching ID (id). Once found, we can proceed to modify the user's data based on the content of <code>req.body</code>. We also checked if first_name, last_name, or email properties exist in <code>req.body</code>. If they do, we can update the corresponding properties of the user object with the new values. This allows us to make selective changes to the user's data without affecting other attributes.</li>
<li>After successfully applying the requested modifications, we send a response back to the client using <code>res.send()</code>. The response includes a message confirming the successful update of the user data, along with the user's ID.</li>
</ul>
<h4 id="heading-how-to-test-the-patch-request"><strong>How to Test the PATCH Request</strong></h4>
<p>Follow these steps to make a PATCH request in Postman:</p>
<ul>
<li>Go to Postman</li>
<li>Open a new request tab</li>
<li>Select "PATCH" from the list of available HTTP methods</li>
<li>Enter URL; the URL will contain the id of the user you want to delete (for example: <a target="_blank" href="http://localhost:5000/users/734a9e75-b3f5-415f-82fb-79b4fdf1a593">http://localhost:5000/users/734a9e75-b3f5-415f-82fb-79b4fdf1a593</a>). If you don’t have a user in your database, you will need to create one and copy the id.</li>
<li>Click on the "Body" tab in the request window and choose the format in which you want to send your PATCH data (for example: JSON, form-data, x-www-form-urlencoded).</li>
<li>Enter the data you want to send in the request body. This data should only include the specific changes you want to make to the resource.</li>
<li>Then click the "Send" button. Postman will send the PATCH request to the specified URL with the provided data.</li>
</ul>
<p><img src="https://lh7-us.googleusercontent.com/K1OH_u-2DOJbkMqlQ3uJow9XNKh0iOmE8fedCCspUiQIHcvm-DmIorFvZcApoDWGSK4YMkFR7RtcHBmzMZaJUyXy7SMDJWxH_PDLFmugGPNQDmKzM6HE3tPOBQZaS1388bZzLlmcTThQa_wy1ZPrm1g" alt="Image" width="600" height="400" loading="lazy">
<em>postman PATCH route test</em></p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>APIs are the linchpin connecting various software components and enabling them to work together seamlessly. They allow them to communicate, share data, and perform tasks. In the context of web development, APIs enable the web to function as we know it today.</p>
<p>In this tutorial, we explored backend development by creating a CRUD API with Node.js and Express. We covered various concepts, like how to set up a development environment, create a server with Express and Node.js, and most importantly, how to handle CRUD operations and test your API using Postman.</p>
<p>While we've covered fundamental aspects of API creation in this tutorial, there is still a vast landscape of knowledge to explore as regards to APIs. These include how to secure your API by adding authentication, how to use middleware, database interaction, API deployment, and a lot more.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use the Geolocation API in JavaScript – with Code Examples ]]>
                </title>
                <description>
                    <![CDATA[ The Geolocation API is a standard API implemented in browsers to retrieve the location of the people who are interacting with a web application. This API enable users to send their location to a web application to enable relevant services, such as se... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-the-javascript-geolocation-api/</link>
                <guid isPermaLink="false">66bd915d621c718d60a31067</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ geolocation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nathan Sebhastian ]]>
                </dc:creator>
                <pubDate>Mon, 26 Feb 2024 22:34:24 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/02/js-geolocation-api-cover.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The Geolocation API is a standard API implemented in browsers to retrieve the location of the people who are interacting with a web application.</p>
<p>This API enable users to send their location to a web application to enable relevant services, such as seeking a restaurant or hotel near the user.</p>
<p>In this article, I'm going to show you how to use the Geolocation API with JavaScript and display a user's current location using a map API. </p>
<p>Let's get started!</p>
<h2 id="heading-how-to-access-the-geolocation-api">How to Access the Geolocation API</h2>
<p>Browsers implement the geolocation API in the <code>navigator.geolocation</code> object. You can check if the browser you use supports this API as follows:</p>
<pre><code class="lang-js"><span class="hljs-keyword">if</span> (<span class="hljs-string">'geolocation'</span> <span class="hljs-keyword">in</span> navigator) {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Geolocation is Available'</span>);
} <span class="hljs-keyword">else</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Geolocation is NOT Available'</span>);
}
</code></pre>
<p>If the browser replies with 'Geolocation is Available', then you can use the methods of the <code>geolocation</code> object to get the user data.</p>
<p>The Geolocation API is only available under a secure HTTPS context, but modern browsers like Chrome and Firefox allow access to this API from localhost for development purposes.</p>
<p>There are two methods you can use to get user data:</p>
<ul>
<li><code>getCurrentPosition()</code>: Returns the current position of the device.</li>
<li><code>watchPosition()</code>: Observes the position of the device continuously until the watcher is stopped.</li>
</ul>
<p>Both methods above receive 3 arguments as follows:</p>
<ul>
<li><code>success</code>: a callback function for when the geolocation data is retrieved (required).</li>
<li><code>error</code>: a callback function for when the method encounters an error (optional).</li>
<li><code>options</code>: an object defining extra parameters when running the method (optional).</li>
</ul>
<p>When the Geolocation API is accessed for the first time, a permission request will pop up near the URL bar as shown below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/geolocation-permission.png" alt="Image" width="600" height="400" loading="lazy">
<em>Requesting Permission to Access User's Location</em></p>
<p>You might be familiar with the pop up above. When you choose to block the request, then the <code>error</code> callback function will be executed by the API.</p>
<p>Otherwise, the <code>success</code> callback will be executed.</p>
<h2 id="heading-how-to-get-users-current-position">How to Get User's Current Position</h2>
<p>To get the user's current position, you can call the <code>getCurrentPosition()</code> from the <code>navigator.geolocation</code> object as shown below:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">success</span>(<span class="hljs-params">position</span>) </span>{
  <span class="hljs-built_in">console</span>.log(position);
}

navigator.geolocation.getCurrentPosition(success);
</code></pre>
<p>The <code>getCurrentPosition()</code> method will send the <code>position</code> object to the <code>success()</code> function above.</p>
<p>The <code>position</code> object contains the location coordinates and timestamp showing when the location was retrieved. </p>
<p>Below is an example of the <code>position</code> object:</p>
<pre><code class="lang-js">{
  <span class="hljs-attr">coords</span>: {
    <span class="hljs-attr">latitude</span>: <span class="hljs-number">1.314</span>,
    <span class="hljs-attr">longitude</span>: <span class="hljs-number">103.84425</span>
    <span class="hljs-attr">altitude</span>: <span class="hljs-literal">null</span>
  },
  <span class="hljs-attr">timestamp</span>: <span class="hljs-number">1708674456885</span>
}
</code></pre>
<p>Using the latitude and longitude information, you can pinpoint the user's location and provide relevant information and services.</p>
<p>For example, let's see how you can send a request to the <a target="_blank" href="https://www.openstreetmap.org/">OpenStreetMap</a> website and pin the user's current location using the data from Geolocation API.</p>
<p>OpenStreetMap is an open source project providing a free geographic map of the whole earth.</p>
<p>You need to create an HTML document with the following body content:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"getLocation"</span>&gt;</span>Get Location<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">br</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"locationResult"</span> <span class="hljs-attr">target</span>=<span class="hljs-string">"_blank"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre>
<p>When the user clicks on the <em>Get Location</em> button above, we will access the Geolocation API, retrieve the user location, and provide a link to see the user's location on a map.</p>
<p>Next, create a <code>&lt;script&gt;</code> tag before the closing <code>&lt;/body&gt;</code> tag and write the following JavaScript code:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">const</span> locationResult = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'#locationResult'</span>);
  <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'#getLocation'</span>).addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
    locationResult.textContent = <span class="hljs-string">'Retrieving User Location...'</span>

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">success</span>(<span class="hljs-params">position</span>) </span>{
      <span class="hljs-keyword">let</span> { coords } = position;
      locationResult.textContent = <span class="hljs-string">'See my location on a map'</span>;
      locationResult.href = <span class="hljs-string">`https://www.openstreetmap.org?mlat=<span class="hljs-subst">${coords.latitude}</span>&amp;mlon=<span class="hljs-subst">${coords.longitude}</span>`</span>;
    }

    navigator.geolocation.getCurrentPosition(success);
  });
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>When the button is clicked, we'll run the <code>getCurrentPosition()</code> method and set the <code>href</code> attribute of the <code>&lt;a&gt;</code> tag to the OpenStreetMap website, passing along the <code>latitude</code> and <code>longitude</code> data under the <code>mlat</code> and <code>mlong</code> query string.</p>
<p>Visiting the link would show a map of the current location as shown below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/geolocation-getCurrentPosition.png" alt="OpenStreetMap Using Geolocation API data" width="600" height="400" loading="lazy">
<em>The OpenStreetMap Website Pin On the Latitude and Longitude Data</em></p>
<p>And that's how you get the current location of the user. How to process the location information is up to you.</p>
<p>I've created a website where you can test this functionality at <a target="_blank" href="https://nathansebhastian.github.io/js-geolocation-api/">https://nathansebhastian.github.io/js-geolocation-api/</a></p>
<p>Next, let's learn about the <code>watchPosition()</code> method.</p>
<h2 id="heading-how-to-watch-users-position">How to Watch User's Position</h2>
<p>The <code>watchPosition()</code> method will continue watching the device position when called. It will run the <code>success</code> callback function each time the device location changes.</p>
<p>You can call the method as follows:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">success</span>(<span class="hljs-params">position</span>) </span>{
  <span class="hljs-keyword">const</span> { coords } = position;
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Latitude data: '</span> + coords.latitude);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Longitude data: '</span> + coords.longitude);
}

navigator.geolocation.watchPosition(success);
</code></pre>
<p>The <code>watchPosition()</code> method returns an ID number that tracks the watcher. If you want to stop the watcher from sending location data, you need to call the <code>clearWatch()</code> method and pass the ID number:</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">success</span>(<span class="hljs-params">position</span>) </span>{
  <span class="hljs-keyword">const</span> { coords } = position;
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Latitude data: '</span> + coords.latitude);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Longitude data: '</span> + coords.longitude);
}

<span class="hljs-comment">// Store the ID number in a variable</span>
<span class="hljs-keyword">const</span> watcherID = navigator.geolocation.watchPosition(success);

<span class="hljs-comment">// Stop the watcher</span>
navigator.geolocation.clearWatch(watcherID);
</code></pre>
<p>And that's all there is to the <code>watchPosition()</code> method.</p>
<h3 id="heading-how-to-add-the-options-object">How to Add the Options Object</h3>
<p>Next, let's take a look at the optional <code>options</code> object that you can pass to <code>getCurrentPosition()</code> and <code>watchPosition()</code> methods.</p>
<p>The <code>options</code> object allows you to customize the behavior of the methods. There are three options you can set:</p>
<ul>
<li><code>enableHighAccuracy</code>: a Boolean value that instructs the method to provide a more accurate position. This will increase power consumption. The default value is <code>false</code>.</li>
<li><code>timeout</code>: a number value representing how long the method waits for a response. The default value is <code>Infinity</code>, which means the method will wait until a location is available.</li>
<li><code>maximumAge</code>: a number value representing how long the Geolocation API can send the previous location data. The default value is <code>0</code>, so the API always returns the latest location. If set to <code>Infinity</code>, then the API will always return the first location data retrieved.</li>
</ul>
<p>You can use the options object when calling the <code>geolocation</code> methods. </p>
<p>For example:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> options = {
  <span class="hljs-attr">enableHighAccuracy</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// enable high accuracy</span>
  <span class="hljs-attr">timeout</span>: <span class="hljs-number">300000</span>, <span class="hljs-comment">// wait for 5 minutes</span>
};

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">success</span>(<span class="hljs-params">position</span>) </span>{
  <span class="hljs-built_in">console</span>.log(position);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">error</span>(<span class="hljs-params">error</span>) </span>{
  <span class="hljs-built_in">console</span>.log(error);
}

<span class="hljs-comment">// Run the getCurrentPosition() method with custom options</span>
navigator.geolocation.getCurrentPosition(
  success,
  error,
  options
);
</code></pre>
<p>In the code above, the <code>getCurrentPosition()</code> method will use high accuracy mode, and it will wait for 5 minutes for a response from the device.</p>
<h2 id="heading-summary">Summary</h2>
<p>The Geolocation API is a standard JavaScript API that allows a web application to access user location data.</p>
<p>Using the Geolocation data, you can provide services or content relevant to the user's location, such as the nearest public transport or hospital.</p>
<p>If you enjoyed this article and want to learn more from me, I recommend you check out my new book <em><a target="_blank" href="https://codewithnathan.com/beginning-modern-javascript">Beginning Modern JavaScript</a></em>.</p>
<p><a target="_blank" href="https://codewithnathan.com/beginning-modern-javascript"><img src="https://www.freecodecamp.org/news/content/images/2024/01/beginning-js-cover.png" alt="Beginning Modern JavaScript" width="600" height="400" loading="lazy"></a></p>
<p>The book is designed to be easy for beginners and accessible to anyone looking to learn JavaScript. It provides a step-by-step gentle guide to help you understand how to use JavaScript to create a dynamic web application.</p>
<p>Here's my promise: <em>You will actually feel like you understand what you're doing with JavaScript.</em></p>
<p>See you in other articles!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ JavaScript Fetch API For Beginners – Explained With Code Examples ]]>
                </title>
                <description>
                    <![CDATA[ The Fetch API is a JavaScript function that you can use to send a request to any Web API URL and get a response. In this article, I'm going to show you how to make HTTP requests to external APIs using the JavaScript Fetch API. You're going to learn h... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/javascript-fetch-api-for-beginners/</link>
                <guid isPermaLink="false">66bd917b6d46d5e73b73e91f</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nathan Sebhastian ]]>
                </dc:creator>
                <pubDate>Fri, 23 Feb 2024 12:14:53 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/02/javascript-fetch-cover.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The Fetch API is a JavaScript function that you can use to send a request to any Web API URL and get a response.</p>
<p>In this article, I'm going to show you how to make HTTP requests to external APIs using the JavaScript Fetch API. You're going to learn how to create GET, POST, PUT/PATCH, and DELETE requests using the Fetch API.</p>
<p>To get the most out of this article, you need to have a good understanding of JavaScript promises. You can read my <a target="_blank" href="https://www.freecodecamp.org/news/javascript-promise-object-explained/">JavaScript Promises</a> article if you need a refresher.</p>
<ul>
<li><a class="post-section-overview" href="#heading-how-the-fetch-api-works">How the Fetch API Works</a></li>
<li><a class="post-section-overview" href="#heading-how-to-send-a-get-request-using-the-fetch-api">How to Send a GET Request</a></li>
<li><a class="post-section-overview" href="#heading-how-to-send-a-post-request-using-the-fetch-api">How to Send a POST Request</a></li>
<li><a class="post-section-overview" href="#heading-how-to-send-a-put-request">How to Send a PUT Request</a></li>
<li><a class="post-section-overview" href="#heading-how-to-send-a-patch-request">How to Send a PATCH Request</a></li>
<li><a class="post-section-overview" href="#heading-how-to-send-a-delete-request">How to Send a DELETE Request</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-asyncawait-with-the-fetch-api">How to Use Async/Await With the Fetch API</a></li>
<li><a class="post-section-overview" href="#heading-run-code-example">Run Code Examples</a></li>
<li><a class="post-section-overview" href="#heading-summary">Summary</a></li>
</ul>
<p>Let's get started!</p>
<h2 id="heading-how-the-fetch-api-works">How the Fetch API Works</h2>
<p>To send a request similar to that of an HTML form, you only need to pass the URL where you want to send the data to as an argument to the <code>fetch()</code> function:</p>
<pre><code class="lang-js">fetch(<span class="hljs-string">'&lt;Your URL&gt;'</span>, {})
</code></pre>
<p>The <code>fetch()</code> function accepts two parameters:</p>
<ol>
<li>The URL to send the request to (this is a required parameter).</li>
<li>The options to set in the request. You can set the request method here (this is an optional parameter).</li>
</ol>
<p>Under the hood, the <code>fetch()</code> function returns a <code>Promise</code>, so you need to add the <code>.then()</code> and <code>.catch()</code> methods.</p>
<p>When the request returns a response, the <code>then()</code> method will be called. If the request returns an error, then the <code>catch()</code> method will be executed:</p>
<pre><code class="lang-js">fetch(<span class="hljs-string">'&lt;Your URL&gt;'</span>, {})
  .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> {
    <span class="hljs-comment">// Handle Fetch response here.</span>
  })
  .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> {
    <span class="hljs-comment">// If there's an error, handle it here</span>
  })
</code></pre>
<p>Inside the <code>.then()</code> and <code>.catch()</code> methods, you pass a callback function to execute when the respective methods are called. </p>
<p>The <code>.catch()</code> method can be omitted from Fetch API. It's used only when Fetch can't make a request to the API, such as no network connection or the URL not found.</p>
<h2 id="heading-how-to-send-a-get-request-using-the-fetch-api">How to Send a GET Request Using the Fetch API</h2>
<p>The GET request is an HTTP request used to ask for specific data from an API, such as when you need </p>
<p>In the following example, we're going to hit a dummy URL located at https://jsonplaceholder.typicode.com to request for a user registered on the site:</p>
<pre><code class="lang-js">fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/users/1'</span>)
  .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(response))
  .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(error));
</code></pre>
<p>The code above will give the following response:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/fetch-readable-stream.png" alt="Image" width="600" height="400" loading="lazy">
<em>Fetch Request Response</em></p>
<p>Here, you can see that the body property contains a <code>ReadableStream</code>. To use the <code>ReadableStream</code> in our JavaScript application, we need to convert it to call the <code>json()</code> method:</p>
<pre><code class="lang-js">fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/users/1'</span>)
  .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
  .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(data))
</code></pre>
<p>The <code>json()</code> method converts the <code>ReadableStream</code> into a JavaScript object. The <code>data</code> variable above will be printed as follows:</p>
<pre><code class="lang-js">{
  <span class="hljs-string">"id"</span>: <span class="hljs-number">1</span>,
  <span class="hljs-string">"name"</span>: <span class="hljs-string">"Leanne Graham"</span>,
  <span class="hljs-string">"username"</span>: <span class="hljs-string">"Bret"</span>,
  <span class="hljs-string">"email"</span>: <span class="hljs-string">"Sincere@april.biz"</span>,
  <span class="hljs-string">"address"</span>: {
    <span class="hljs-string">"street"</span>: <span class="hljs-string">"Kulas Light"</span>,
    <span class="hljs-string">"suite"</span>: <span class="hljs-string">"Apt. 556"</span>,
    <span class="hljs-string">"city"</span>: <span class="hljs-string">"Gwenborough"</span>,
    <span class="hljs-string">"zipcode"</span>: <span class="hljs-string">"92998-3874"</span>,
    <span class="hljs-string">"geo"</span>: {
      <span class="hljs-string">"lat"</span>: <span class="hljs-string">"-37.3159"</span>,
      <span class="hljs-string">"lng"</span>: <span class="hljs-string">"81.1496"</span>
    }
  },
  <span class="hljs-string">"phone"</span>: <span class="hljs-string">"1-770-736-8031 x56442"</span>,
  <span class="hljs-string">"website"</span>: <span class="hljs-string">"hildegard.org"</span>,
  <span class="hljs-string">"company"</span>: {
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"Romaguera-Crona"</span>,
    <span class="hljs-string">"catchPhrase"</span>: <span class="hljs-string">"Multi-layered client-server neural-net"</span>,
    <span class="hljs-string">"bs"</span>: <span class="hljs-string">"harness real-time e-markets"</span>
  }
}
</code></pre>
<p>Now that you have the <code>data</code> object, you can use this value in any way you want. For example, if you want to display the user name and email in HTML, here's how you do it:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">id</span>=<span class="hljs-string">'user-name'</span>&gt;</span>Waiting for data<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">id</span>=<span class="hljs-string">'user-email'</span>&gt;</span>Waiting for data<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
    fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/users/1'</span>)
      .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
      .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
        <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'#user-name'</span>).textContent = data.name
        <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'#user-email'</span>).textContent = data.email
      })
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre>
<p>In the above code, the Fetch API will run as soon as the browser loads the HTML document.</p>
<p>After processing the <code>response</code> into a <code>data</code> object, JavaScript will change the text of the <code>&lt;h1&gt;</code> and <code>&lt;h2&gt;</code> elements above to reflect the <code>name</code> and <code>email</code> of the user.</p>
<p>If you run the code above, you'll get the following output:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/fetch-get-response.png" alt="Image" width="600" height="400" loading="lazy">
<em>The Fetch Request Output Displayed in the Browser</em></p>
<p>And that's how you send a GET request using Fetch and display the returned data in HTML.</p>
<p>Note that depending on the request you are asking for, an API might return a different type of data. </p>
<p>In this example, the typicode API sends back an object, but you might also get an array when you request more than one unit of data.</p>
<p>If you access the URL at <a target="_blank" href="https://jsonplaceholder.typicode.com/users">https://jsonplaceholder.typicode.com/users</a>, you'll see that the API respond with an array of objects. </p>
<p>To handle an array of objects, you can iterate over the array and show the data in HTML as follows:</p>
<pre><code class="lang-js"><span class="hljs-comment">// example</span>
</code></pre>
<p>You need to know the data type returned by the API to handle it correctly.</p>
<h2 id="heading-how-to-send-a-post-request-using-the-fetch-api">How to Send a POST Request Using the Fetch API</h2>
<p>If you want to send a POST request instead of a GET request, you need to define the second argument when calling the function, which is the option object.</p>
<p>Inside the option object, define a <code>method</code> property as follows:</p>
<pre><code class="lang-js">fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/users'</span>, {
  <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>, <span class="hljs-comment">// Set method here</span>
})
.then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
.then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(data))
</code></pre>
<p>When you send a POST method, you need to set the request header and body properties to ensure a smooth process.</p>
<p>For the header, you need to add the <code>Content-Type</code> property and set it to <code>application/json</code>.</p>
<p>The data you want to send should be put inside the <code>body</code> property in a JSON format. See the example below:</p>
<pre><code class="lang-js">fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/users'</span>, {
  <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
  <span class="hljs-attr">headers</span>: {
    <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
  },
  <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
    <span class="hljs-attr">name</span>: <span class="hljs-string">'Nathan Sebhastian'</span>,
    <span class="hljs-attr">email</span>: <span class="hljs-string">'ns@mail.com'</span>
  }),
}).then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
  .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(data))
</code></pre>
<p>In the example above, we sent a POST request to create a new user. In the <code>body</code> property, a regular JavaScript object was converted into a JSON string by calling the <code>JSON.stringify()</code> method.</p>
<p>JSON is one of the formats computers use to communicate with each other on the internet.</p>
<p>The response from the typicode.com API would be similar as follows:</p>
<pre><code class="lang-js">{
  <span class="hljs-string">"name"</span>: <span class="hljs-string">"Nathan Sebhastian"</span>,
  <span class="hljs-string">"email"</span>: <span class="hljs-string">"ns@mail.com"</span>,
  <span class="hljs-string">"id"</span>: <span class="hljs-number">11</span>
}
</code></pre>
<p>This means that we successfully created a new user. Since typicode.com is a fake API, the user won't really be added, but it will pretend as if it was.</p>
<h2 id="heading-how-to-send-a-put-request">How to Send a PUT Request</h2>
<p>A PUT request is used to create a new resource or update an existing one.</p>
<p>For example, if you want to update an existing user <code>name</code> and <code>email</code> data. You can use a PUT request to do so:</p>
<pre><code class="lang-js">fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/users/1'</span>, {
  <span class="hljs-attr">method</span>: <span class="hljs-string">'PUT'</span>,
  <span class="hljs-attr">headers</span>: {
    <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
  },
  <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
    <span class="hljs-attr">name</span>: <span class="hljs-string">'Nathan Sebhastian'</span>,
    <span class="hljs-attr">email</span>: <span class="hljs-string">'nathan@mail.com'</span>
  }),
}).then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
  .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(data))
</code></pre>
<p>The request above will receive the following response:</p>
<pre><code class="lang-js">{
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"Nathan Sebhastian"</span>,
    <span class="hljs-string">"email"</span>: <span class="hljs-string">"nathan@mail.com"</span>,
    <span class="hljs-string">"id"</span>: <span class="hljs-number">1</span>
}
</code></pre>
<p>Because user data with an <code>id</code> value of 1 already exists, the PUT request above updates that data.</p>
<p>Next, let's look at the PATCH request.</p>
<h2 id="heading-how-to-send-a-patch-request">How to Send a PATCH Request</h2>
<p>The PATCH request is sent when you need to update an existing request. </p>
<p>For example, if you want to change the <code>name</code> and <code>username</code> data from an existing user.</p>
<p>Here's an example of sending a PATCH request to typicode.com:</p>
<pre><code class="lang-js">fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/users/1'</span>, {
  <span class="hljs-attr">method</span>: <span class="hljs-string">'PATCH'</span>,
  <span class="hljs-attr">headers</span>: {
    <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
  },
  <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({ 
    <span class="hljs-attr">name</span>: <span class="hljs-string">'Nathan Sebhastian'</span>,
    <span class="hljs-attr">username</span>: <span class="hljs-string">'nsebhastian'</span>
  }),
}).then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
  .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(data))
</code></pre>
<p>The request above will give the following response:</p>
<pre><code class="lang-js">{
    <span class="hljs-string">"id"</span>: <span class="hljs-number">1</span>,
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"Nathan Sebhastian"</span>,
    <span class="hljs-string">"username"</span>: <span class="hljs-string">"nsebhastian"</span>,
    <span class="hljs-string">"email"</span>: <span class="hljs-string">"Sincere@april.biz"</span>,
    <span class="hljs-comment">// ... the rest of user data</span>
}
</code></pre>
<p>Above, you can see that the <code>name</code> and <code>username</code> property values are updated using the body of the PATCH request.</p>
<h2 id="heading-how-to-send-a-delete-request">How to Send a DELETE Request</h2>
<p>The DELETE request is used when you want to request that a resource be removed permanently from the server.</p>
<p>To run a DELETE request with Fetch, you only need to specify the URL for the resource to delete and the <code>method: 'DELETE'</code> property as follows:</p>
<pre><code class="lang-js">fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/users/1'</span>, {
  <span class="hljs-attr">method</span>: <span class="hljs-string">'DELETE'</span>,
}).then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
  .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(data))
</code></pre>
<p>The request above will delete a user data that has an id value of 1.</p>
<p>The API might respond with some message to confirm that the resource has been removed. But since typicode.com is a dummy API, it will send back an empty JavaScript Object <code>{}</code> .</p>
<h2 id="heading-how-to-use-asyncawait-with-the-fetch-api">How to Use Async/Await With the Fetch API</h2>
<p>Since Fetch returns a <code>Promise</code> object, this means that you can also use the <code>async</code>/<code>await</code> syntax to replace the <code>.then()</code> and <code>.catch()</code> methods.</p>
<p>Here's an example of sending a GET request using Fetch in async/await syntax:</p>
<pre><code class="lang-js"><span class="hljs-keyword">try</span> {
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'https://jsonplaceholder.typicode.com/users/1'</span>);
  <span class="hljs-keyword">const</span> json = <span class="hljs-keyword">await</span> response.json();
  <span class="hljs-built_in">console</span>.log(json);
} <span class="hljs-keyword">catch</span> (error) {
  <span class="hljs-built_in">console</span>.log(error);
}
</code></pre>
<p>Handling a Fetch response using <code>async</code>/<code>await</code> looks cleaner because you don't have to use the <code>.then()</code> and <code>.catch()</code> callbacks.</p>
<p>If you need a refresher on async/await, you can read my <a target="_blank" href="https://www.freecodecamp.org/news/javascript-async-await/">JavaScript Async/Await</a> article.</p>
<h2 id="heading-run-code-example">Run Code Example</h2>
<p>I've also created an example website that shows you how to run these 5 HTTP request protocols at <a target="_blank" href="https://nathansebhastian.github.io/js-fetch-api/">https://nathansebhastian.github.io/js-fetch-api/</a></p>
<p>Check it out and study the returned <code>data</code> object. To know what API requests you can send to a specific API, you need to look at the documentation of that API project.</p>
<h2 id="heading-summary">Summary</h2>
<p>The Fetch API allows you to access APIs and perform a network request using standard request methods such as GET, POST, PUT, PATCH, and DELETE.</p>
<p>The Fetch API returns a promise, so you need to chain the function call with <code>.then()</code> and <code>.catch()</code> methods, or use the async/await syntax.</p>
<p>And that's how the Fetch API works! If you enjoyed this article, you might want to check out my <a target="_blank" href="https://codewithnathan.com/beginning-modern-javascript"><em>Beginning Modern JavaScript</em></a> book to level up your JavaScript skill:</p>
<p><a target="_blank" href="https://codewithnathan.com/beginning-modern-javascript"><img src="https://www.freecodecamp.org/news/content/images/2024/01/beginning-js-cover.png" alt="Beginning Modern JavaScript" width="600" height="400" loading="lazy"></a></p>
<p>The book is designed to be easy for beginners and accessible to anyone looking to learn JavaScript. It provides a step-by-step gentle guide that will help you understand how to use JavaScript to create a dynamic web application.</p>
<p>Here's my promise: You will actually feel like you understand what you're doing with JavaScript.</p>
<p>See you in other articles!</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
