<?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>Tue, 07 Jul 2026 22:44:45 +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 Build Secure-by-Default Node.js APIs ]]>
                </title>
                <description>
                    <![CDATA[ Most security problems I've shipped in my career weren't exotic. They weren't nation-state attacks or clever zero-days. They were boring. A missing limit here, a forgotten timeout there, a string comp ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-secure-by-default-node-js-apis/</link>
                <guid isPermaLink="false">6a3c3fc702ebd10f875ab988</guid>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Devlin Duldulao ]]>
                </dc:creator>
                <pubDate>Wed, 24 Jun 2026 20:36:23 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/edebad91-82e3-4d67-b136-bbb99859a393.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most security problems I've shipped in my career weren't exotic. They weren't nation-state attacks or clever zero-days. They were boring. A missing limit here, a forgotten timeout there, a string comparison that leaked a secret one millisecond at a time.</p>
<p>The boring stuff is what gets you, because the boring stuff is what everyone agrees to fix "later," and later has a way of never arriving.</p>
<p>My favorite personal example (favorite in the way a scar is your favorite) was an internal API that compared an access token with a plain equality check and had no limit on request size. It ran fine for a year. It ran fine right up until someone curious discovered they could both fingerprint the token comparison and post a body large enough to make the server sweat.</p>
<p>Neither bug was sophisticated. Both would have been a complete non-event if something had simply refused to let me do the wrong thing in the first place.</p>
<p>This tutorial shows you how to add practical guardrails around every HTTP API you build, regardless of framework. You'll write them by hand, in plain Node.js, with no dependencies. This will let you see exactly what each one does and why.</p>
<p>By the end, you'll have a small server that survives a lot more contact with the public internet than the version most of us shipped early in our careers.</p>
<p>This tutorial uses plain JavaScript so anyone can copy and run it. If you use TypeScript, you can add types afterward. You need Node 22 or newer, a basic understanding of HTTP requests and responses, and a terminal for testing the examples.</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-what-youll-build">What You'll Build</a></p>
</li>
<li><p><a href="#heading-how-to-start-with-the-naive-server">How to Start with the Naïve Server</a></p>
</li>
<li><p><a href="#heading-how-to-limit-the-request-body">How to Limit the Request Body</a></p>
</li>
<li><p><a href="#heading-how-to-time-out-slow-requests">How to Time Out Slow Requests</a></p>
</li>
<li><p><a href="#heading-how-to-parse-json-safely-and-block-prototype-pollution">How to Parse JSON Safely and Block Prototype Pollution</a></p>
</li>
<li><p><a href="#heading-how-to-set-security-headers-on-every-response">How to Set Security Headers on Every Response</a></p>
</li>
<li><p><a href="#heading-how-to-compare-secrets-in-constant-time">How to Compare Secrets in Constant Time</a></p>
</li>
<li><p><a href="#heading-how-to-validate-input-as-a-gate-not-a-suggestion">How to Validate Input as a Gate, Not a Suggestion</a></p>
</li>
<li><p><a href="#heading-how-to-fail-without-leaking-and-log-so-you-can-see-it">How to Fail Without Leaking and Log So You Can See It</a></p>
</li>
<li><p><a href="#heading-how-to-put-it-all-together">How to Put It All Together</a></p>
</li>
<li><p><a href="#heading-how-to-handle-cors-correctly">How to Handle CORS Correctly</a></p>
</li>
<li><p><a href="#heading-what-this-tutorial-doesnt-cover">What This Tutorial Doesn't Cover</a></p>
</li>
<li><p><a href="#heading-why-defaults-beat-checklists">Why Defaults Beat Checklists</a></p>
</li>
<li><p><a href="#heading-an-honest-note-on-frameworks">An Honest Note on Frameworks</a></p>
</li>
<li><p><a href="#heading-the-takeaway-checklist">The Takeaway Checklist</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li><p>Node.js 22 or newer</p>
</li>
<li><p>Basic familiarity with HTTP requests and responses</p>
</li>
<li><p>A terminal and curl, Postman, or a similar client</p>
</li>
</ul>
<p>This isn't a guide to making your API unhackable. It's a practical guide to avoiding the easy attacks and building safer defaults.</p>
<h2 id="heading-what-youll-build">What You'll Build</h2>
<p>A plain Node.js API with request size limits, request timeouts, safe JSON parsing, security headers, timing-safe secret comparison, validation, and error handling.</p>
<h2 id="heading-how-to-start-with-the-naive-server">How to Start with the Naïve Server</h2>
<p>Here's the kind of server I wrote when I was younger and braver and wrong. It reads a JSON body and echoes it back. Pretend it's the start of a real API.</p>
<pre><code class="language-ts">import http from "node:http";

const server = http.createServer((req, res) =&gt; {
  let body = "";
  req.on("data", (chunk) =&gt; (body += chunk));
  req.on("end", () =&gt; {
    const data = JSON.parse(body || "{}");
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ youSent: data }));
  });
});

server.listen(3000, () =&gt; console.log("listening on http://localhost:3000"));
</code></pre>
<p>It works. You can curl it and it answers. It's also a small disaster, and here's the incomplete list of why:</p>
<ul>
<li><p>It reads an unbounded body into memory. Send it a few gigabytes and you have a denial-of-service with no effort.</p>
</li>
<li><p><code>JSON.parse</code> throws on bad input, which here means an uncaught exception that can take the process down.</p>
</li>
<li><p>There's no timeout. A client that sends one byte per minute can hold a connection hostage.</p>
</li>
<li><p>It sets no security headers and happily advertises that it's a Node server.</p>
</li>
<li><p>It parses untrusted JSON straight into an object with no checks, which opens the door to prototype pollution downstream.</p>
</li>
</ul>
<p>You're going to fix each of these issues. The fixes are small. The point is to make them habits, not heroics.</p>
<h2 id="heading-how-to-limit-the-request-body">How to Limit the Request Body</h2>
<p>The first rule of accepting input from strangers is to decide, in advance, how much input you're willing to accept. If you don't set a limit, the limit is "however much RAM the server has," and someone will find that out for you.</p>
<p>There are two layers here. The first is the <code>Content-Length</code> header, which the client sends to declare how big the body is. You can reject early based on it. But you must never trust it alone, because a client can lie or simply not send it.</p>
<p>The real defense is to count bytes as they stream in and stop the moment they cross your line.</p>
<pre><code class="language-ts">const MAX_BODY_BYTES = 100 * 1024; // 100 KB is plenty for most JSON APIs

function readBody(req, limit = MAX_BODY_BYTES) {
  return new Promise((resolve, reject) =&gt; {
    // Cheap early rejection if the client is honest about being too big.
    const declared = Number(req.headers["content-length"]);
    if (Number.isFinite(declared) &amp;&amp; declared &gt; limit) {
      reject(httpError(413, "Payload too large"));
      return;
    }

    let size = 0;
    const chunks = [];

    req.on("data", (chunk) =&gt; {
      size += chunk.length;
      if (size &gt; limit) {
        reject(httpError(413, "Payload too large"));
        req.destroy(); // stop reading; we are done with this client
        return;
      }
      chunks.push(chunk);
    });

    req.on("end", () =&gt; resolve(Buffer.concat(chunks)));
    req.on("error", reject);
  });
}

function httpError(statusCode, message) {
  return Object.assign(new Error(message), { statusCode });
}
</code></pre>
<p>A few things worth noticing here. You accumulate <code>Buffer</code> chunks and only join them at the end, rather than concatenating strings, because string concatenation forces an early decode and can mangle multibyte UTF-8 characters that happen to land on a chunk boundary.</p>
<p>You also call <code>req.destroy()</code> as soon as you cross the limit, so you don't keep pulling bytes you've already decided to refuse.</p>
<p>Pick a limit that matches the route. A JSON API that creates a user doesn't need a 50 MB body. A file upload endpoint is a different conversation, and there you would stream to disk or object storage instead of buffering in memory at all. The mistake is having no limit, not having the wrong one.</p>
<h2 id="heading-how-to-time-out-slow-requests">How to Time Out Slow Requests</h2>
<p>Once you have a body limit, the next trick an attacker reaches for is to be slow instead of large. This is the family of attacks named after slowloris, a sad-looking primate that moves very slowly, which is rude to the animal but accurate about the attack.</p>
<p>The idea is to open many connections and feed them bytes at a glacial pace, never finishing, so the server keeps each one alive waiting politely. Do that enough times and you've exhausted the connection pool without sending anything that looks malicious.</p>
<p>Node has built-in defenses for this, and the defaults are generous and worth tightening for an API.</p>
<pre><code class="language-ts">const server = http.createServer(handler);

// Total time allowed to receive the entire request (headers + body).
server.requestTimeout = 30_000; // 30 seconds

// Time allowed to receive just the headers. Slowloris lives here.
server.headersTimeout = 10_000; // 10 seconds

// Idle socket timeout: kill connections that go quiet.
server.setTimeout(60_000);
</code></pre>
<p>Those three lines handle the network layer. But there's a second kind of slow: your own handler. A database query that hangs, an outbound call to a third party that never answers, a regular expression that decided to think about its life choices. You want a ceiling on how long a single request is allowed to occupy a worker, and you want to be able to cancel the work when that ceiling is hit.</p>
<p>The modern tool for cancellation in Node is <code>AbortController</code>. Here's a small wrapper that gives every handler a deadline and a signal it can pass down to anything that supports cancellation, like <code>fetch</code>.</p>
<pre><code class="language-ts">function withTimeout(handler, ms = 15_000) {
  return async (req, res) =&gt; {
    const controller = new AbortController();
    const timer = setTimeout(() =&gt; controller.abort(), ms);
    try {
      await handler(req, res, controller.signal);
    } finally {
      clearTimeout(timer);
    }
  };
}
</code></pre>
<p>Now a handler can do <code>await fetch(url, { signal })</code> and the request gets cut off if it blows the deadline, instead of camping on a worker forever.</p>
<p>The discipline to learn here is that any time you talk to something outside your process, you give it a deadline. Networks fail in the most boring way possible: by hanging, not by erroring. A timeout turns a hang into a clean error you can handle.</p>
<h2 id="heading-how-to-parse-json-safely-and-block-prototype-pollution">How to Parse JSON Safely and Block Prototype Pollution</h2>
<p>This is the one people skip because it sounds theoretical, and then it shows up in a CVE with their stack in it.</p>
<p>First, the easy half. <code>JSON.parse</code> throws a <code>SyntaxError</code> on malformed input. In the naïve server, that throw was uncaught and could crash the process. So we wrap parsing and turn a parse failure into a clean 400.</p>
<pre><code class="language-ts">function parseJson(buffer) {
  if (buffer.length === 0) return {};
  let text = buffer.toString("utf8");
  try {
    return JSON.parse(text, reviver);
  } catch {
    throw httpError(400, "Invalid JSON body");
  }
}
</code></pre>
<p>Now the interesting half: that <code>reviver</code> argument. Prototype pollution is an attack where a request payload reaches up and modifies <code>Object.prototype</code>, the object that almost every object in your program inherits from. If an attacker can set a property there, they can set it on effectively everything at once.</p>
<p>It's easier to believe once you see it. Here's a recursive merge function, the kind people write all the time to apply updates onto an existing record:</p>
<pre><code class="language-ts">function merge(target, source) {
  for (const key in source) {
    if (source[key] &amp;&amp; typeof source[key] === "object") {
      if (!target[key]) target[key] = {};
      merge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
}
</code></pre>
<p>Looks harmless. Now feed it a payload an attacker controls:</p>
<pre><code class="language-ts">const evil = JSON.parse('{"__proto__": {"isAdmin": true}}');
const account = {};
merge(account, evil);

console.log(account.isAdmin);   // undefined, account itself is fine
console.log(({}).isAdmin);      // true  &lt;-- every object is now "admin"
</code></pre>
<p>That second line is the horror. You never touched <code>({})</code>. You polluted the shared prototype, so a brand new empty object now reports <code>isAdmin: true</code>. If somewhere later your code does <code>if (user.isAdmin)</code> on an object that didn't explicitly set that field, congratulations! Everyone is an admin. The <code>__proto__</code> key tricked the merge into walking up into the prototype that all objects share.</p>
<p>The defense is to refuse the dangerous keys before they ever get into your data. The cleanest way at parse time is the reviver, a function <code>JSON.parse</code> calls for every key as it builds the result. Return <code>undefined</code> for a key and it gets dropped.</p>
<pre><code class="language-ts">const FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);

function reviver(key, value) {
  if (FORBIDDEN_KEYS.has(key)) return undefined;
  return value;
}
</code></pre>
<p>That is it. Three key names, blocked at the door, and the merge above becomes harmless because the payload never carries <code>__proto__</code> past the parser.</p>
<p>For extra defense in depth, you can also build internal lookup objects with <code>Object.create(null)</code>, which creates an object with no prototype at all, or use a <code>Map</code> when the keys are user-controlled. And if you want a belt to go with the suspenders, <code>Object.freeze(Object.prototype)</code> early in your process start will make the whole class of attack fail loudly.</p>
<p>I wouldn't rely on freezing alone, because some libraries get unhappy about it, but blocking the keys costs you nothing and should be the default.</p>
<h2 id="heading-how-to-set-security-headers-on-every-response">How to Set Security Headers on Every Response</h2>
<p>Browsers will defend your users for you, but only if you tell them to. That instruction comes as a small set of response headers. For an API that returns JSON, the list is short and the defaults are strict, which is exactly how you want it.</p>
<pre><code class="language-ts">function secureHeaders(res) {
  // Do not let the browser guess content types. Stops a JSON response
  // from being treated as HTML or a script.
  res.setHeader("X-Content-Type-Options", "nosniff");

  // Clickjacking defense: do not allow this response inside a frame.
  res.setHeader("X-Frame-Options", "DENY");
  res.setHeader("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'");

  // Do not leak the full URL (which may contain ids or tokens) on navigation.
  res.setHeader("Referrer-Policy", "no-referrer");

  // Only meaningful over HTTPS: force HTTPS for two years, including subdomains.
  res.setHeader("Strict-Transport-Security", "max-age=63072000; includeSubDomains");

  // Stop advertising what you are running. Free reconnaissance for nobody.
  res.removeHeader("X-Powered-By");
}
</code></pre>
<p>A quick tour, because cargo-culting headers is how you end up with a Content Security Policy that does nothing.</p>
<p><code>X-Content-Type-Options: nosniff</code> stops the browser from second-guessing your <code>Content-Type</code>, which closes a sneaky path where a response gets reinterpreted as something executable. <code>X-Frame-Options</code> and the <code>frame-ancestors</code> directive both refuse to let your responses be embedded in a frame, which is the heart of clickjacking. For a pure JSON API, <code>default-src 'none'</code> is a strong and appropriate CSP, because an API has no business loading scripts, styles, or images.</p>
<p><code>Referrer-Policy: no-referrer</code> keeps your URLs, which sometimes carry ids you would rather not gossip about, from being sent to other sites. <code>Strict-Transport-Security</code> only matters once you're on HTTPS, but once you are, it stops downgrade attacks by telling the browser to refuse plain HTTP.</p>
<p>And removing <code>X-Powered-By</code> is a tiny thing that just denies an attacker a free hint about what to throw at you.</p>
<p>The reason to wrap this in a function and call it on every response is that "every response" is the part humans forget. Make it one call you do at the top of the request, before you can get distracted.</p>
<h2 id="heading-how-to-compare-secrets-in-constant-time">How to Compare Secrets in Constant Time</h2>
<p>Here's a bug that looks completely fine and is completely broken:</p>
<pre><code class="language-ts">if (providedApiKey === expectedApiKey) {
  // grant access
}
</code></pre>
<p>The problem is that <code>===</code> on strings is allowed to be fast. It compares character by character and returns <code>false</code> the instant it finds a mismatch. That means a wrong guess that gets the first character right takes very slightly longer to reject than one that gets it wrong immediately.</p>
<p>That timing difference is tiny, but it's measurable over enough requests, and it lets an attacker recover a secret one character at a time. This is a real attack, it has a name (a timing attack), and the fix is built into Node.</p>
<p>You want a comparison whose running time doesn't depend on where the first difference is. Node gives you <code>crypto.timingSafeEqual</code> for exactly this. It has one sharp edge: it throws if the two buffers have different lengths, and length itself is a leak.</p>
<p>The clean way to handle both problems at once is to hash both inputs to a fixed size first, then compare the hashes.</p>
<pre><code class="language-ts">import { timingSafeEqual, createHash } from "node:crypto";

function safeCompare(a, b) {
  // Hashing normalizes length (so timingSafeEqual is happy) and hides
  // the length of the real secret from a timing observer.
  const ha = createHash("sha256").update(String(a)).digest();
  const hb = createHash("sha256").update(String(b)).digest();
  return timingSafeEqual(ha, hb);
}
</code></pre>
<p>Use this anywhere you compare a value a stranger supplied against a secret you hold: API keys, webhook signatures, password reset tokens, session identifiers. The rule of thumb is simple: if being wrong about the comparison would let someone in, don't use <code>===</code>.</p>
<p>One important caveat so you don't misuse this. For user passwords, don't store them and compare with this function. Passwords get hashed with a slow, purpose-built algorithm so that even if your database leaks, the hashes are expensive to crack.</p>
<p>Node ships <code>scrypt</code> for this in <code>node:crypto</code>, and <code>bcrypt</code> and <code>argon2</code> are popular libraries. The <code>safeCompare</code> above is for comparing high-entropy secrets like tokens and keys, not for human-chosen passwords.</p>
<h2 id="heading-how-to-validate-input-as-a-gate-not-a-suggestion">How to Validate Input as a Gate, Not a Suggestion</h2>
<p>Everything so far has been about surviving hostile input at the transport level. Validation is about refusing input that doesn't match the shape your code expects, before that input reaches your business logic.</p>
<p>A surprising amount of "weird production behavior" is just a handler that assumed a field was a string and got an array, or assumed a number and got the string "NaN".</p>
<p>You can hand-roll validation for a small API, and it's healthy to see what that looks like before you reach for a library:</p>
<pre><code class="language-ts">function expect(condition, message) {
  if (!condition) throw httpError(400, message);
}

function parseCreateUser(data) {
  expect(typeof data.email === "string" &amp;&amp; data.email.includes("@"), "email is required");
  expect(typeof data.password === "string", "password is required");
  expect(data.password.length &gt;= 12, "password must be at least 12 characters");
  // Return only the fields you actually want. Ignore everything else.
  return { email: data.email, password: data.password };
}
</code></pre>
<p>Notice the last line. You build a fresh object with only the fields you asked for, rather than passing <code>data</code> straight through. This quietly closes a mass-assignment hole, where a client sends <code>{"email": "...", "password": "...", "role": "admin"}</code> and a careless handler writes the whole object into the database, role included. If you only copy the fields you meant to accept, the extra ones never matter.</p>
<p>For anything beyond a few routes, a schema library pays for itself fast. Zod and Valibot are the popular choices, and both let you describe the shape once and get validation plus inferred types out of it.</p>
<pre><code class="language-ts">import { z } from "zod";

const CreateUser = z
  .object({
    email: z.string().email(),
    password: z.string().min(12),
  })
  .strict(); // reject unknown keys instead of ignoring them

const result = CreateUser.safeParse(data);
if (!result.success) throw httpError(400, "Validation failed");
const user = result.data;
</code></pre>
<p>That <code>.strict()</code> call is doing the same mass-assignment defense as our hand-rolled version, but declaratively. Whether you hand-roll it or use a library, the principle is the same: input is guilty until proven to match a shape you defined on purpose.</p>
<h2 id="heading-how-to-fail-without-leaking-and-log-so-you-can-see-it">How to Fail Without Leaking and Log So You Can See It</h2>
<p>Errors are going to happen. The real question is what your server says when they do, and whether you can reconstruct what went wrong afterward.</p>
<p>There are two common ways to get this wrong, and they are opposites. Either you hand attackers a map of your internals, or you blind yourself during an incident.</p>
<p>The leaking version looks like this, and yes, I've shipped it:</p>
<pre><code class="language-ts">catch (err) {
  res.writeHead(500);
  res.end(err.stack); // please do not
}
</code></pre>
<p>That stack trace can include file paths, library versions, query fragments, and sometimes secrets that got interpolated into an error message. It's a free briefing for whoever is poking at you.</p>
<p>The rule is blunt: a 500 should tell the client nothing useful, and tell you everything, through your logs.</p>
<p>The opposite failure is hiding the error so thoroughly that when it happens in production at 2am you have nothing to go on.</p>
<p>The fix for both problems is the same small idea: a request id. It's a short unique value you attach to each request, return to the client in a header, and include in every log line for that request. When a user reports "I got an error and it said request abc123," you can find exactly that request in your logs in seconds.</p>
<pre><code class="language-ts">import { randomUUID } from "node:crypto";

function withRequestId(req, res) {
  const requestId = req.headers["x-request-id"] ?? randomUUID();
  res.setHeader("X-Request-Id", requestId);
  return requestId;
}

function log(level, requestId, message, extra = {}) {
  // Structured logs: one JSON object per line, easy to search and ship.
  console.log(
    JSON.stringify({ level, requestId, message, ...extra, at: new Date().toISOString() }),
  );
}

function sendError(res, err, requestId) {
  const status = err.statusCode ?? 500;
  const message = status === 500 ? "Internal Server Error" : err.message;
  if (status === 500) {
    log("error", requestId, "unhandled error", { stack: err.stack });
  }
  if (!res.headersSent) {
    res.writeHead(status, { "Content-Type": "application/json" });
  }
  res.end(JSON.stringify({ error: message, requestId }));
}
</code></pre>
<p>The client gets the <code>requestId</code> but never the details. They can quote it to support, but they can't read your stack trace. Accept an incoming <code>X-Request-Id</code> when a trusted upstream set one, so a single request keeps the same id as it moves across your services, but generate your own whenever it is missing. Structured logs, one JSON object per line, are worth the slight ugliness, because they're trivial to filter and feed into a log aggregator, which a pile of freeform <code>console.log</code> calls is not.</p>
<p>One note on environments. It's fine, even helpful, to return richer error detail when you run locally. Just gate it on an explicit environment check and make production the strict default, so the worst outcome of a misconfiguration is too little information leaked, never too much.</p>
<h2 id="heading-how-to-put-it-all-together">How to Put It All Together</h2>
<p>None of these guardrails is impressive on its own. The power is in having all of them on, by default, on every route, so that the safe path is the path of least resistance.</p>
<p>Here is the naïve server from the start, rebuilt with everything we covered. It's still tiny. It's just no longer naïve.</p>
<pre><code class="language-ts">import http from "node:http";
import { timingSafeEqual, createHash } from "node:crypto";

const MAX_BODY_BYTES = 100 * 1024;
const FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"]);

function httpError(statusCode, message) {
  return Object.assign(new Error(message), { statusCode });
}

function reviver(key, value) {
  return FORBIDDEN_KEYS.has(key) ? undefined : value;
}

function readBody(req, limit = MAX_BODY_BYTES) {
  return new Promise((resolve, reject) =&gt; {
    const declared = Number(req.headers["content-length"]);
    if (Number.isFinite(declared) &amp;&amp; declared &gt; limit) {
      return reject(httpError(413, "Payload too large"));
    }
    let size = 0;
    const chunks = [];
    req.on("data", (chunk) =&gt; {
      size += chunk.length;
      if (size &gt; limit) {
        reject(httpError(413, "Payload too large"));
        req.destroy();
        return;
      }
      chunks.push(chunk);
    });
    req.on("end", () =&gt; resolve(Buffer.concat(chunks)));
    req.on("error", reject);
  });
}

function parseJson(buffer) {
  if (buffer.length === 0) return {};
  try {
    return JSON.parse(buffer.toString("utf8"), reviver);
  } catch {
    throw httpError(400, "Invalid JSON body");
  }
}

function secureHeaders(res) {
  res.setHeader("X-Content-Type-Options", "nosniff");
  res.setHeader("X-Frame-Options", "DENY");
  res.setHeader("Content-Security-Policy", "default-src 'none'; frame-ancestors 'none'");
  res.setHeader("Referrer-Policy", "no-referrer");
  res.setHeader("Strict-Transport-Security", "max-age=63072000; includeSubDomains");
  res.removeHeader("X-Powered-By");
}

function safeCompare(a, b) {
  const ha = createHash("sha256").update(String(a)).digest();
  const hb = createHash("sha256").update(String(b)).digest();
  return timingSafeEqual(ha, hb);
}

function sendJson(res, status, payload) {
  if (!res.headersSent) {
    res.writeHead(status, { "Content-Type": "application/json" });
  }
  res.end(JSON.stringify(payload));
}

function sendError(res, err) {
  const status = err.statusCode ?? 500;
  // Never leak internal error details on a 500. Log them, do not ship them.
  const message = status === 500 ? "Internal Server Error" : err.message;
  if (status === 500) console.error(err);
  sendJson(res, status, { error: message });
}

const API_KEY = process.env.API_KEY ?? "dev-only-key";

async function handler(req, res) {
  secureHeaders(res);

  // A single protected route, as an example.
  if (req.method === "POST" &amp;&amp; req.url === "/users") {
    const provided = req.headers["x-api-key"] ?? "";
    if (!safeCompare(provided, API_KEY)) {
      throw httpError(401, "Unauthorized");
    }

    const data = parseJson(await readBody(req));

    if (typeof data.email !== "string" || !data.email.includes("@")) {
      throw httpError(400, "email is required");
    }
    if (typeof data.password !== "string" || data.password.length &lt; 12) {
      throw httpError(400, "password must be at least 12 characters");
    }

    // Only the fields we asked for. No mass assignment.
    const user = { email: data.email };
    return sendJson(res, 201, { created: user });
  }

  throw httpError(404, "Not found");
}

const server = http.createServer((req, res) =&gt; {
  handler(req, res).catch((err) =&gt; sendError(res, err));
});

server.requestTimeout = 30_000;
server.headersTimeout = 10_000;
server.setTimeout(60_000);

server.listen(3000, () =&gt; console.log("listening on http://localhost:3000"));
</code></pre>
<p>Read that top to bottom and notice how the security isn't a separate "security middleware" bolted on at the end. It's woven into the normal flow.</p>
<p>The body has a limit. The JSON is parsed safely. The headers go out every time. The API key check is timing-safe. The validation runs before any logic. The error handler refuses to leak internals. And the whole thing is still small enough to hold in your head, which matters, because security you can't understand is security you'll eventually disable by accident.</p>
<p>Try breaking it. Send a huge body and watch the 413. Send <code>{"__proto__": {"isAdmin": true}}</code> and confirm <code>({}).isAdmin</code> is still <code>undefined</code> afterward. Send a wrong API key and notice you can't tell from the response time how close you were. That last one is invisible by design, which is the whole point.</p>
<h2 id="heading-how-to-handle-cors-correctly">How to Handle CORS Correctly</h2>
<p>CORS, which stands for Cross-Origin Resource Sharing, is one of the most misunderstood security features in web development, and the misunderstanding is the dangerous kind.</p>
<p>Here's the part people get wrong: CORS doesn't protect your server. It's not a firewall. It's a browser feature that decides whether JavaScript running on one website is allowed to read the response from your API on another. Your server stays perfectly reachable from curl, from Postman, and from any other server, CORS headers or not.</p>
<p>What that means in practice is that the most common "fix" people apply is also the most common mistake:</p>
<pre><code class="language-ts">res.setHeader("Access-Control-Allow-Origin", "*"); // understand this before you ship it
</code></pre>
<p>A wildcard says "any website's JavaScript may read my responses." For a genuinely public, read-only API with no credentials, that can be perfectly fine. For anything that uses cookies or returns data tied to a logged-in user, it's a mistake, and browsers will refuse to combine <code>*</code> with credentials anyway.</p>
<p>The correct approach is to allow only the origins you actually trust:</p>
<pre><code class="language-ts">const ALLOWED_ORIGINS = new Set(["https://app.example.com"]);

function applyCors(req, res) {
  const origin = req.headers.origin;
  if (origin &amp;&amp; ALLOWED_ORIGINS.has(origin)) {
    res.setHeader("Access-Control-Allow-Origin", origin);
    res.setHeader("Vary", "Origin"); // so a cache does not mix origins up
    res.setHeader("Access-Control-Allow-Credentials", "true");
  }
}
</code></pre>
<p>Keep this mental model: CORS loosens the browser's default protection in a controlled way. Setting it to <code>*</code> doesn't make your API more exposed to attacks from other servers, because servers were never restricted in the first place. It makes your data readable by any web page a victim happens to visit, which is a privacy and data-exposure decision, not a "make the console error go away" decision. Decide it on purpose, origin by origin.</p>
<h2 id="heading-what-this-tutorial-doesnt-cover">What This Tutorial Doesn't Cover</h2>
<p>Honesty time, because a tutorial that pretends to be totally complete is doing you a disservice. The guardrails above are the baseline, not the finish line.</p>
<p>Here's what's deliberately out of scope and where to look next:</p>
<ul>
<li><p><strong>Authentication and authorization:</strong> You checked one API key. Real apps need sessions or tokens, and a real story for who is allowed to do what. That is a whole topic on its own.</p>
</li>
<li><p><strong>Rate limiting:</strong> A single client shouldn't be able to hammer your login route ten thousand times a minute. In-memory counters work for one instance. Behind a load balancer you need a shared store like Redis.</p>
</li>
<li><p><strong>Outbound request safety (SSRF):</strong> The moment your server makes requests to URLs a user supplied, you have a new attack surface: someone can point you at internal addresses or the cloud metadata endpoint. That deserves its own article.</p>
</li>
<li><p><strong>TLS:</strong> Everything HSTS-related assumes you actually terminate HTTPS somewhere, whether that's the runtime, a reverse proxy, or your platform.</p>
</li>
<li><p><strong>Logging and monitoring:</strong> You can't respond to what you can't see. Structured logs with request ids are the unglamorous foundation of every incident response that went well.</p>
</li>
</ul>
<p>Each of these is a future tutorial, and each one follows the same philosophy as this one: make the safe choice the default, and make the unsafe choice something you have to go out of your way to do.</p>
<h2 id="heading-why-defaults-beat-checklists">Why Defaults Beat Checklists</h2>
<p>You might be wondering why I keep saying "by default" instead of just handing you a checklist and wishing you luck. The reason is that I've watched a lot of checklists lose to a deadline.</p>
<p>A checklist is a list of things a human has to remember to do, correctly, every single time, forever. That includes the junior dev who joined last week, and the senior dev who's exhausted and shipping a hotfix at midnight.</p>
<p>Security that depends on perfect human memory is security that quietly degrades the moment the team gets busy, which is precisely the moment an attacker is hoping for.</p>
<p>A default is a different kind of thing. A default is what happens when nobody does anything at all. If the safe behavior is the default, then forgetting produces a safe app. If the unsafe behavior is the default, then forgetting produces a vulnerability, and people forget constantly, because they're human and they have forty other things on their plate.</p>
<p>This is exactly why the helpers in this article are wrappers you call once at the top of a request, instead of steps you sprinkle through your handlers and hope you got them all. It's the same reason frameworks that take security seriously turn protections on and make you opt out, rather than leaving them off and making you opt in.</p>
<p>The wording sounds like a small difference. Measured across a real team over a real year, the difference in outcomes is enormous. Design it so the lazy path and the safe path are the same path, and you'll be surprised how secure "lazy" can be.</p>
<h2 id="heading-an-honest-note-on-frameworks">An Honest Note on Frameworks</h2>
<p>If wiring all of this by hand every time sounds tedious, that's exactly the right instinct, and it's why some frameworks ship these protections on by default so you don't have to remember them.</p>
<p>Full disclosure: I maintain one of them, an open-source project called DaloyJS. I'm not here to sell it, and everything in this article is plain Node that works the same no matter what you build on.</p>
<p>I mention it only because the lesson that produced it is the lesson of this whole piece: the defaults are the product. A framework that makes you opt into safety will, statistically, be run by someone who forgot to.</p>
<p>Whether you use a framework or roll your own, copy the helpers above into your project today. They're dependency-free and they will quietly prevent a category of bad days.</p>
<h2 id="heading-the-takeaway-checklist">The Takeaway Checklist</h2>
<p>If you remember nothing else, remember this list and put it somewhere your team will see it:</p>
<ul>
<li><p><strong>Limit the body:</strong> Count bytes as they stream, reject past your cap, never trust <code>Content-Length</code> alone.</p>
</li>
<li><p><strong>Time out everything:</strong> Tighten Node's request and header timeouts, and give every outbound call a deadline with <code>AbortController</code>.</p>
</li>
<li><p><strong>Parse JSON defensively:</strong> Catch parse errors into a clean 400, and strip <code>__proto__</code>, <code>constructor</code>, and <code>prototype</code> with a reviver.</p>
</li>
<li><p><strong>Set security headers on every response:</strong> Wrap them in one function so "every response" actually means every response.</p>
</li>
<li><p><strong>Compare secrets in constant time:</strong> Use <code>crypto.timingSafeEqual</code> on hashed inputs, never <code>===</code>, and use a real password hash for passwords.</p>
</li>
<li><p><strong>Validate input as a gate:</strong> Define the shape on purpose, reject what doesn't match, and copy only the fields you asked for.</p>
</li>
</ul>
<p>None of this is clever. That's the best thing about it. The boring stuff is what gets you, so make the boring stuff automatic, and go spend your cleverness on the parts of your product that actually need it.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Choose the Best Stock Market API for FinTech Projects and AI Agents  ]]>
                </title>
                <description>
                    <![CDATA[ Choosing a stock API looks simple until the project becomes real. At first, you only need a few prices. You send a request, get JSON back, load it into pandas, and move on. But the moment that API sta ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-choose-the-best-stock-market-api-for-fintech-projects-and-ai-agents/</link>
                <guid isPermaLink="false">6a24b9c567572e709df513c8</guid>
                
                    <category>
                        <![CDATA[ fintech ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #Stock market ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ai agents ]]>
                    </category>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nikhil Adithyan ]]>
                </dc:creator>
                <pubDate>Sun, 07 Jun 2026 00:22:29 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/e1f20d3c-eaf8-49e9-be53-4cc99eb971ec.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Choosing a stock API looks simple until the project becomes real.</p>
<p>At first, you only need a few prices. You send a request, get JSON back, load it into pandas, and move on. But the moment that API starts powering a backtester, dashboard, screener, valuation tool, or AI assistant, the decision becomes much more serious.</p>
<p>A backtester needs adjusted historical prices, splits, dividends, and stable time series. A dashboard needs fresh quotes, clean fields, and reliable responses. A stock screener needs fundamentals, ratios, and company metadata. An AI agent needs structured data that it can retrieve and use without guessing.</p>
<p>That's why I wouldn't start by comparing endpoint counts or pricing pages. Those matter, but they're not the first question.</p>
<p>The first question is: <strong>what are you building?</strong></p>
<p>In this article, we’ll walk through how to choose a stock market API based on the workflow it needs to support. Then we’ll build a practical stock research workflow in Python using Alpha Vantage to see how prices, fundamentals, technical indicators, and AI-ready access can fit together in one project.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-why-stock-api-choice-depends-on-the-workflow">Why Stock API Choice Depends On The Workflow</a></p>
<ul>
<li><p><a href="#heading-1-if-you-are-building-a-backtester">1. If You Are Building A Backtester</a></p>
</li>
<li><p><a href="#heading-2-if-you-are-building-a-dashboard">2. If You Are Building A Dashboard</a></p>
</li>
<li><p><a href="#heading-3-if-you-are-building-a-stock-screener">3. If You Are Building A Stock Screener</a></p>
</li>
<li><p><a href="#heading-4-if-you-are-building-a-valuation-or-research-tool">4. If You Are Building A Valuation Or Research Tool</a></p>
</li>
<li><p><a href="#heading-5-if-you-are-building-an-ai-assistant-or-agent">5. If You Are Building An AI Assistant Or Agent</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-what-a-modern-stock-market-data-workflow-actually-requires">What A Modern Stock Market Data Workflow Actually Requires</a></p>
</li>
<li><p><a href="#heading-building-a-practical-stock-research-workflow-with-alpha-vantage">Building A Practical Stock Research Workflow With Alpha Vantage</a></p>
<ul>
<li><p><a href="#heading-step-1-fetch-adjusted-historical-prices">Step 1: Fetch Adjusted Historical Prices</a></p>
</li>
<li><p><a href="#heading-step-2-add-company-or-fundamental-data">Step 2: Add Company Or Fundamental Data</a></p>
</li>
<li><p><a href="#heading-step-3-add-technical-indicators">Step 3: Add Technical Indicators</a></p>
</li>
<li><p><a href="#heading-step-4-combine-everything-into-a-research-ready-table">Step 4: Combine Everything Into A Research-Ready Table</a></p>
</li>
<li><p><a href="#heading-step-5-connect-the-workflow-to-ai-agents-with-mcp">Step 5: Connect The Workflow To AI Agents With MCP</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-where-each-provider-fits-in-the-stock-api-workflow">Where Each Provider Fits In The Stock API Workflow</a></p>
</li>
<li><p><a href="#provider-breakdown-through-a-workflow-lens">Provider Breakdown Through A Workflow Lens</a></p>
<ul>
<li><p><a href="#heading-1-when-the-project-needs-several-data-layers-alpha-vantage">1. When The Project Needs Several Data Layers: Alpha Vantage</a></p>
</li>
<li><p><a href="#heading-2-when-the-workflow-is-institutional-bloomberg-api">2. When The Workflow Is Institutional: Bloomberg API</a></p>
</li>
<li><p><a href="#heading-3-when-the-product-needs-investor-relations-widgets-quotemedia">3. When The Product Needs Investor Relations Widgets: QuoteMedia</a></p>
</li>
<li><p><a href="#heading-4-when-the-workflow-is-global-historical-research-eodhd">4. When The Workflow Is Global Historical Research: EODHD</a></p>
</li>
<li><p><a href="#5-when-the-workflow-needs-us-fundamentals-intrinio">5. When The Workflow Needs US Fundamentals: Intrinio</a></p>
</li>
<li><p><a href="#heading-6-when-the-workflow-needs-enterprise-data-delivery-xignite">6. When The Workflow Needs Enterprise Data Delivery: Xignite</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-final-checklist-before-choosing-a-stock-api">Final Checklist Before Choosing A Stock API</a></p>
</li>
<li><p><a href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ul>
<h2 id="heading-why-stock-api-choice-depends-on-the-workflow"><strong>Why Stock API Choice Depends On The Workflow</strong></h2>
<p>A stock API should be judged by the workflow it supports, not by how long its feature list looks. The same provider can be a good fit for one project and a weak fit for another.</p>
<p>A clean historical dataset matters more for a backtester than a live quote endpoint. A dashboard has different problems. It needs fresh responses, predictable fields, and rate limits that don't collapse once users start refreshing the page.</p>
<p>Here is how I would think about it.</p>
<h3 id="heading-1-if-you-are-building-a-backtester">1. If You Are Building A Backtester</h3>
<h4 id="heading-start-with-historical-data-quality">Start with historical data quality.</h4>
<p>A backtest needs adjusted prices, splits, dividends, long history, and stable time series. If those pieces are wrong, the backtest can still run, but the results may be misleading.</p>
<p>For this workflow, real-time data is usually secondary. Clean historical data matters more than fast quotes.</p>
<h3 id="heading-2-if-you-are-building-a-dashboard">2. If You Are Building A Dashboard</h3>
<h4 id="heading-start-with-freshness-and-reliability">Start with freshness and reliability.</h4>
<p>A dashboard needs quote data that updates consistently, fields that don't change unexpectedly, and rate limits that can handle repeated requests. A failed request in a notebook is annoying. A failed request in a user-facing dashboard is a product problem.</p>
<p>You also need to check whether the data can be displayed to users. Licensing becomes part of the workflow once the dashboard is public.</p>
<h3 id="heading-3-if-you-are-building-a-stock-screener">3. If You Are Building A Stock Screener</h3>
<h4 id="heading-start-with-fundamentals-and-structured-fields">Start with fundamentals and structured fields.</h4>
<p>A screener needs more than prices. It may need ratios, company profiles, sector data, market cap, earnings, and symbol coverage across many companies.</p>
<p>The hard part is comparison. If fields are inconsistent across tickers, the screener becomes a cleanup project before it becomes a useful tool.</p>
<h3 id="heading-4-if-you-are-building-a-valuation-or-research-tool">4. If You Are Building A Valuation Or Research Tool</h3>
<h4 id="heading-start-with-financial-statements">Start with financial statements.</h4>
<p>A valuation workflow usually needs income statements, balance sheets, cash flow statements, earnings history, and historical fundamentals. Price data gives market context, but the business data does the heavier work.</p>
<p>This is where depth matters. The latest numbers are useful, but trends across multiple periods are often more important.</p>
<h3 id="heading-5-if-you-are-building-an-ai-assistant-or-agent">5. If You Are Building An AI Assistant Or Agent</h3>
<h4 id="heading-start-with-structure">Start with structure.</h4>
<p>An AI agent shouldn't guess financial data from memory. It needs predictable API responses, clear schemas, and tool access it can use reliably.</p>
<p>This is where MCP-style workflows matter. If an agent can call a tool, retrieve a quote, pull fundamentals, or fetch a time series cleanly, the API becomes part of the agent’s reasoning loop.</p>
<p>The practical point is simple: choose the API around the system you're building. Once the workflow is clear, the rest of the decision becomes much easier.</p>
<h2 id="heading-what-a-modern-stock-market-data-workflow-actually-requires"><strong>What A Modern Stock Market Data Workflow Actually Requires</strong></h2>
<p>A modern stock data workflow is rarely just one API call.</p>
<p>You might start with market data, but most useful projects eventually need more layers. A research dashboard may need fundamentals. A screener may need technical indicators. An AI assistant may need structured responses that it can retrieve through a tool.</p>
<p>A simple way to think about the workflow is:</p>
<p><code>Market Data -&gt; Fundamentals -&gt; Indicators -&gt; Structured Responses -&gt; Programmatic Workflow -&gt; AI/Agent Access</code></p>
<p>Each layer solves a different problem.</p>
<ul>
<li><p><strong>Market data</strong> gives you prices, volume, returns, and historical movement.</p>
</li>
<li><p><strong>Fundamentals</strong> add business context through revenue, margins, cash flow, earnings, and company details.</p>
</li>
<li><p><strong>Indicators</strong> help convert raw prices into features that can support screening, research, or signal testing.</p>
</li>
<li><p><strong>Structured responses</strong> make the data easier to parse, join, and reuse.</p>
</li>
<li><p><strong>Programmatic workflows</strong> turn the raw API response into tables, charts, models, dashboards, or research outputs.</p>
</li>
<li><p><strong>AI or agent access</strong> lets an assistant call tools, retrieve current data, and work with structured financial context instead of relying only on static knowledge.</p>
</li>
</ul>
<p>This is why stock API choice matters beyond the first request. The API is not only there to return data but to support the way the project grows after the prototype.</p>
<h2 id="heading-building-a-practical-stock-research-workflow-with-alpha-vantage"><strong>Building A Practical Stock Research Workflow With Alpha Vantage</strong></h2>
<p>Now let’s turn the framework into something practical.</p>
<p>For this section, we’ll use Alpha Vantage as the implementation API because it gives us the main layers we need for this workflow: adjusted historical prices, company data, technical indicators, and MCP-style access for AI agents.</p>
<p>The goal isn't to test every endpoint. The goal is to build a small research workflow that shows what a useful stock API should help us do.</p>
<p>We’ll build this in five steps:</p>
<ol>
<li><p>Fetch adjusted historical prices.</p>
</li>
<li><p>Add company or fundamental data.</p>
</li>
<li><p>Add a technical indicator.</p>
</li>
<li><p>Combine everything into a research-ready table.</p>
</li>
<li><p>Connect the workflow to an AI-agent setup using MCP.</p>
</li>
</ol>
<p>By the end, we should have a simple but practical stock research table that can support a screener, dashboard, research notebook, or AI assistant.</p>
<h3 id="heading-step-1-fetch-adjusted-historical-prices">Step 1: Fetch Adjusted Historical Prices</h3>
<p>Adjusted prices are the first thing I would check for any research or backtesting workflow. Raw prices can break around stock splits or dividends, while adjusted prices keep the series more useful for return calculations.</p>
<p>Let’s fetch daily adjusted price data for Apple.</p>
<pre><code class="language-python">import requests
import pandas as pd

api_key = 'YOUR ALPHA VANTAGE API KEY'

symbol = 'AAPL'

url = f'https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&amp;symbol={symbol}&amp;outputsize=compact&amp;apikey={api_key}'

response = requests.get(url)
data = response.json()

prices = pd.DataFrame(data['Time Series (Daily)']).T

prices.index = pd.to_datetime(prices.index)
prices = prices.sort_index()

prices = prices.rename(columns={
    '1. open': 'open',
    '2. high': 'high',
    '3. low': 'low',
    '4. close': 'close',
    '5. adjusted close': 'adjusted_close',
    '6. volume': 'volume',
    '7. dividend amount': 'dividend',
    '8. split coefficient': 'split'
})

price_cols = ['open', 'high', 'low', 'close', 'adjusted_close', 'volume', 'dividend', 'split']
prices[price_cols] = prices[price_cols].astype(float)

prices.tail()
</code></pre>
<p>The output gives us a clean daily price table as you can see in the image below:</p>
<img src="https://cdn.hashnode.com/uploads/covers/5f362fe21017f7317167b14c/903925ac-462b-4684-9b51-98b6f6173f74.png" alt="903925ac-462b-4684-9b51-98b6f6173f74" style="display:block;margin:0 auto" width="1878" height="556" loading="lazy">

<p>For a chart, you may only need <code>close</code>. For research or backtesting, I would usually work with <code>adjusted_close</code> because it handles corporate actions more safely. Next, we can convert the time series into a few basic price features.</p>
<pre><code class="language-python">latest_price = prices['adjusted_close'].iloc[-1] 
return_30d = prices['adjusted_close'].pct_change(30).iloc[-1] 
volatility_30d = prices['adjusted_close'].pct_change().tail(30).std() 

price_features = {'symbol': symbol, 'latest_price': latest_price, 'return_30d': return_30d, 'volatility_30d': volatility_30d}
price_features
</code></pre>
<p>This returns:</p>
<pre><code class="language-plaintext">{'symbol': 'AAPL',
 'latest_price': 312.06,
 'return_30d': 0.18583097277442007,
 'volatility_30d': 0.012845143800989936}
</code></pre>
<p>This is already more useful than a raw API response. We now have a small set of price features that can feed a dashboard, screener, research table, or AI-assisted stock analysis workflow.</p>
<h3 id="heading-step-2-add-company-or-fundamental-data">Step 2: Add Company Or Fundamental Data</h3>
<p>Price data tells us how the stock moved, but it doesn't tell us much about the company behind the ticker. For a screener, valuation tool, or research workflow, we need some business context too.</p>
<p>Alpha Vantage’s OVERVIEW endpoint gives company-level fields like sector, industry, market cap, PE ratio, EPS, profit margin, and other summary metrics. Let’s pull those fields and keep only the ones we need for this workflow.</p>
<pre><code class="language-python">overview_url = f'https://www.alphavantage.co/query?function=OVERVIEW&amp;symbol={symbol}&amp;apikey={api_key}'

response = requests.get(overview_url)
overview = response.json()

fundamental_features = {
    'symbol': symbol,
    'name': overview.get('Name'),
    'sector': overview.get('Sector'),
    'industry': overview.get('Industry'),
    'market_cap': overview.get('MarketCapitalization'),
    'pe_ratio': overview.get('PERatio'),
    'eps': overview.get('EPS'),
    'profit_margin': overview.get('ProfitMargin'),
    'beta': overview.get('Beta')
}

fundamental_features
</code></pre>
<p>This returns:</p>
<pre><code class="language-plaintext">{'symbol': 'AAPL',
 'name': 'Apple Inc',
 'sector': 'TECHNOLOGY',
 'industry': 'CONSUMER ELECTRONICS',
 'market_cap': 4583336182000.0,
 'pe_ratio': 37.73,
 'eps': 8.27,
 'profit_margin': 0.272,
 'beta': 1.065}
</code></pre>
<p>Now we have two layers: price behavior from the time series data and business context from the company overview. The next step is to add a technical indicator so the table includes a market-derived signal as well.</p>
<h3 id="heading-step-3-add-technical-indicators">Step 3: Add Technical Indicators</h3>
<p>Fundamentals give us business context, but many research workflows also need market-derived signals. A simple example is the relative strength index, or RSI, which is often used to measure recent momentum.</p>
<p>Alpha Vantage has a RSI endpoint, so we can pull the indicator directly instead of calculating it from scratch.</p>
<pre><code class="language-python">rsi_url = f'https://www.alphavantage.co/query?function=RSI&amp;symbol={symbol}&amp;interval=daily&amp;time_period=14&amp;series_type=close&amp;apikey={api_key}'

response = requests.get(rsi_url)
rsi_data = response.json()

rsi = pd.DataFrame(rsi_data['Technical Analysis: RSI']).T

rsi.index = pd.to_datetime(rsi.index)
rsi = rsi.sort_index()
rsi['RSI'] = rsi['RSI'].astype(float)

latest_rsi = rsi['RSI'].iloc[-1]

indicator_features = {
    'symbol': symbol,
    'rsi_14': latest_rsi
}

indicator_features
</code></pre>
<p>This returns:</p>
<pre><code class="language-plaintext">{'symbol': 'AAPL', 'rsi_14': 79.0043}
</code></pre>
<p>Now the workflow has three layers:</p>
<ul>
<li><p>price behavior from adjusted historical data</p>
</li>
<li><p>business context from company fundamentals</p>
</li>
<li><p>momentum context from a technical indicator</p>
</li>
</ul>
<p>None of these is enough on its own. Together, they start to look like a usable research workflow instead of a raw API test.</p>
<h3 id="heading-step-4-combine-everything-into-a-research-ready-table">Step 4: Combine Everything Into A Research-Ready Table</h3>
<p>Now we can combine the price, fundamentals, and indicator layers into one table.</p>
<p>This is the part that matters for most real projects. A dashboard, screener, notebook, or AI assistant usually needs a clean object it can reuse, not three separate raw API responses.</p>
<pre><code class="language-python">research_row = {
    **price_features,
    **fundamental_features,
    **indicator_features
}

research_table = pd.DataFrame([research_row])

research_table
</code></pre>
<p>This gives us a single-row research table:</p>
<img src="https://cdn.hashnode.com/uploads/covers/5f362fe21017f7317167b14c/5d659e28-19e3-4455-a1d8-e9bbd02e3ace.png" alt="research table" style="display:block;margin:0 auto" width="1864" height="126" loading="lazy">

<p>This table is simple, but it already supports several use cases.</p>
<p>A screener can filter on <code>pe_ratio</code>, <code>profit_margin</code>, or <code>rsi_14</code>. A dashboard can show price, returns, sector, and market cap. A research notebook can add more tickers and compare them. An AI assistant can receive this as a compact context object instead of parsing multiple API responses on its own.</p>
<p>That's the real benefit of building the workflow this way. The API calls are only the beginning. The useful output is the structured table you create from them.</p>
<h3 id="heading-step-5-connect-the-workflow-to-ai-agents-with-mcp">Step 5: Connect The Workflow To AI Agents With MCP</h3>
<p>The table we created is useful because it has a predictable structure, which is exactly what AI workflows need.</p>
<p>If an agent needs stock context, it shouldn't guess from memory or parse several raw API responses every time. It should call a tool, retrieve the data, and receive something clean enough to use.</p>
<p>A simplified MCP workflow looks like this:</p>
<p><code>User question -&gt; AI agent -&gt; MCP tool call -&gt; Stock API data -&gt; Structured response -&gt; Final answer</code></p>
<p>For example, a user might ask:</p>
<p><em>Is Apple looking expensive compared with its recent momentum?</em></p>
<p>An agent could retrieve price data, fundamentals, and an indicator such as RSI before answering. The important part is not that the model already “knows” the answer. It's that the model can call the right tool and work with current data.</p>
<p>That is where our research table helps:</p>
<pre><code class="language-python">research_table.to_dict(orient='records')[0]
</code></pre>
<p>This returns a compact dictionary:</p>
<pre><code class="language-plaintext">{'symbol': 'AAPL',
 'latest_price': 312.06,
 'return_30d': 0.18583097277442007,
 'volatility_30d': 0.012845143800989936,
 'name': 'Apple Inc',
 'sector': 'TECHNOLOGY',
 'industry': 'CONSUMER ELECTRONICS',
 'market_cap': 4583336182000.0,
 'pe_ratio': 37.73,
 'eps': 8.27,
 'profit_margin': 0.272,
 'beta': 1.065,
 'rsi_14': 79.0043}
</code></pre>
<p>This doesn't replace proper analysis, and it shouldn't be treated as investment advice. But it gives an AI assistant a cleaner starting point than raw JSON, stale model knowledge, or a vague prompt with no data attached.</p>
<p>AI readiness isn't just about saying an API supports agents. The API has to return data that can be retrieved, structured, checked, and passed into a workflow without fragile glue code at every step.</p>
<h2 id="heading-where-each-provider-fits-in-the-stock-api-workflow"><strong>Where Each Provider Fits In The Stock API Workflow</strong></h2>
<p>The workflow we built above is one version of a modern stock data project: prices, fundamentals, indicators, programmatic analysis, and AI-agent access working together.</p>
<p>Other projects may need a narrower or more specialized provider. Here's a practical way to compare the fit:</p>
<table style="min-width:653px"><colgroup><col style="min-width:25px"><col style="width:84px"><col style="width:75px"><col style="width:87px"><col style="width:90px"><col style="width:88px"><col style="width:83px"><col style="width:121px"></colgroup><tbody><tr><td><p><strong>Provider</strong></p></td><td><p><strong>Market Data</strong></p></td><td><p><strong>Fundamentals</strong></p></td><td><p><strong>Technical Indicators</strong></p></td><td><p><strong>Developer Workflow</strong></p></td><td><p><strong>AI / Agent Readiness</strong></p></td><td><p><strong>Workflow Completeness</strong></p></td><td><p><strong>Best Fit</strong></p></td></tr><tr><td><p>Alpha Vantage</p></td><td><p>Strong</p></td><td><p>Strong</p></td><td><p>Strong</p></td><td><p>Strong</p></td><td><p>Strong</p></td><td><p>High</p></td><td><p>Broad technical projects, research tools, screeners, dashboards, and AI-agent workflows</p></td></tr><tr><td><p>Bloomberg API</p></td><td><p>Very strong</p></td><td><p>Strong</p></td><td><p>Moderate</p></td><td><p>Enterprise-focused</p></td><td><p>Enterprise-dependent</p></td><td><p>High</p></td><td><p>Institutions already using Bloomberg internally</p></td></tr><tr><td><p>QuoteMedia</p></td><td><p>Strong</p></td><td><p>Moderate</p></td><td><p>Limited / Moderate</p></td><td><p>Moderate</p></td><td><p>Limited</p></td><td><p>Medium</p></td><td><p>Investor relations websites and embedded market data widgets</p></td></tr><tr><td><p>EODHD</p></td><td><p>Strong</p></td><td><p>Good</p></td><td><p>Good</p></td><td><p>Good</p></td><td><p>Strong</p></td><td><p>High</p></td><td><p>Global EOD history, backtesting, and historical research</p></td></tr><tr><td><p>Intrinio</p></td><td><p>Good</p></td><td><p>Strong</p></td><td><p>Limited / Moderate</p></td><td><p>Good</p></td><td><p>Limited / Moderate</p></td><td><p>Medium / High</p></td><td><p>US fundamentals, valuation tools, and professional datasets</p></td></tr><tr><td><p>Xignite</p></td><td><p>Strong</p></td><td><p>Good</p></td><td><p>Limited / Moderate</p></td><td><p>Enterprise-focused</p></td><td><p>Limited / Moderate</p></td><td><p>Medium / High</p></td><td><p>Enterprise financial applications needing vendor support</p></td></tr></tbody></table>

<p>No provider fits every workflow equally well. The point of this table is to show where the fit is strongest.</p>
<p>Alpha Vantage works well when a project needs several layers together, especially market data, fundamentals, indicators, developer usability, and AI-agent access. EODHD is stronger when the workflow is centered on global historical research. Intrinio fits better when standardized US fundamentals are the main requirement. Bloomberg API and Xignite are more natural for institutional or enterprise environments, while QuoteMedia is more specialized around investor relations and embedded market data widgets.</p>
<p>This is the right way to think about stock APIs: not as one universal winner, but as different tools for different workflow shapes.</p>
<h2 id="heading-provider-breakdown-through-a-workflow-lens"><strong>Provider Breakdown Through A Workflow Lens</strong></h2>
<p>The table gives a quick comparison. This section explains what that means in practice.</p>
<p>Instead of asking which provider is “best” in general, it is better to ask: what kind of workflow is this provider naturally built for?</p>
<h3 id="heading-1-when-the-project-needs-several-data-layers-alpha-vantage">1. When The Project Needs Several Data Layers: Alpha Vantage</h3>
<p>Alpha Vantage fits well when the project needs more than one type of market data in the same workflow.</p>
<p>In the workflow we built earlier, we used:</p>
<ul>
<li><p>adjusted historical prices</p>
</li>
<li><p>company data</p>
</li>
<li><p>technical indicators</p>
</li>
<li><p>structured output for programmatic analysis</p>
</li>
<li><p>a format that can also support AI-agent workflows</p>
</li>
</ul>
<p>That makes Alpha Vantage a flexible fit for stock research notebooks, screeners, dashboards, backtesting workflows, and AI assistants that need market data through tools or MCP-style access.</p>
<p>The main caveat is specialization. If your project needs direct exchange infrastructure, co-location, or a highly specialized institutional setup, you may need a more specialized provider. But for most research, fintech apps, and AI workflows, Alpha Vantage gives enough breadth without forcing you to combine several APIs too early.</p>
<h3 id="heading-2-when-the-workflow-is-institutional-bloomberg-api">2. When The Workflow Is Institutional: Bloomberg API</h3>
<p>Bloomberg API makes sense when the organization already uses Bloomberg internally.</p>
<p>It's best suited for firms that want to connect Bloomberg data with internal tools, reports, models, and risk systems.</p>
<p>This isn't usually the right fit for solo developers or small teams. The cost, licensing, and ecosystem dependency make it more suitable for institutions.</p>
<h3 id="heading-3-when-the-product-needs-investor-relations-widgets-quotemedia">3. When The Product Needs Investor Relations Widgets: QuoteMedia</h3>
<p>QuoteMedia fits products where the main need is public-facing market data display.</p>
<p>That can include:</p>
<ul>
<li><p>investor relations pages</p>
</li>
<li><p>quote widgets</p>
</li>
<li><p>embedded charts</p>
</li>
<li><p>company stock pages</p>
</li>
<li><p>market data modules for public websites</p>
</li>
</ul>
<p>This is different from building a programmatic research workflow. QuoteMedia makes more sense when presentation and embedded financial data are the core product requirement.</p>
<h3 id="heading-4-when-the-workflow-is-global-historical-research-eodhd">4. When The Workflow Is Global Historical Research: EODHD</h3>
<p>EODHD fits well when the project needs broad historical data across global markets.</p>
<p>It's useful for long-horizon backtesting, global screeners, and research workflows that depend on end-of-day data from many exchanges.</p>
<p>The tradeoff is cleanup. Global data often brings differences in symbols, exchange calendars, currencies, and local market conventions. That's manageable, but it should be expected.</p>
<h3 id="heading-5-when-the-workflow-needs-us-fundamentals-intrinio">5. When The Workflow Needs US Fundamentals: Intrinio</h3>
<p>Intrinio fits well when standardized US fundamentals are the center of the product.</p>
<p>It's useful for:</p>
<ul>
<li><p>valuation tools</p>
</li>
<li><p>earnings dashboards</p>
</li>
<li><p>fundamentals-based screeners</p>
</li>
<li><p>professional US equity research workflows</p>
</li>
</ul>
<p>The main thing to check is dataset fit. Before building around Intrinio, I would look closely at the specific datasets, access terms, and coverage levels the product needs.</p>
<h3 id="heading-6-when-the-workflow-needs-enterprise-data-delivery-xignite">6. When The Workflow Needs Enterprise Data Delivery: Xignite</h3>
<p>Xignite fits larger financial applications that need formal vendor support.</p>
<p>This can include banks, brokerages, wealth platforms, and enterprise fintech products where support, contracts, reliability, and data relationships matter as much as the endpoint itself.</p>
<p>For smaller developer projects, it may feel heavier than necessary. For enterprise products, that structure can be exactly the point.</p>
<h2 id="heading-final-checklist-before-choosing-a-stock-api"><strong>Final Checklist Before Choosing A Stock API</strong></h2>
<p>Before choosing a provider, I would run through this checklist.</p>
<table style="min-width:428px"><colgroup><col style="min-width:25px"><col style="width:403px"></colgroup><tbody><tr><td><p><strong>Question</strong></p></td><td><p><strong>Why It Matters</strong></p></td></tr><tr><td><p>What am I building?</p></td><td><p>A backtester, dashboard, screener, valuation tool, and AI assistant all need different things.</p></td></tr><tr><td><p>Do I need real-time, delayed, or historical data?</p></td><td><p>Real-time access matters only if the workflow actually needs it.</p></td></tr><tr><td><p>Do I need adjusted prices?</p></td><td><p>For backtesting and research, adjusted prices are usually non-negotiable.</p></td></tr><tr><td><p>Do I need fundamentals?</p></td><td><p>Screeners, valuation tools, and research dashboards usually need company data, not just prices.</p></td></tr><tr><td><p>Do I need technical indicators?</p></td><td><p>Signal testing, filters, and momentum-style analysis may need indicators directly from the API or calculated separately.</p></td></tr><tr><td><p>How many symbols will I query?</p></td><td><p>One ticker in a notebook is easy. Hundreds of tickers can expose rate-limit and performance issues quickly.</p></td></tr><tr><td><p>Will users see the data?</p></td><td><p>If yes, licensing, display rights, storage rules, and redistribution terms matter before the product goes live.</p></td></tr><tr><td><p>Is the response easy to parse in Python or other programming languages?</p></td><td><p>Clean JSON can save a lot of cleanup work once the project grows.</p></td></tr><tr><td><p>Can it support AI or agent workflows?</p></td><td><p>AI assistants need structured responses, tool compatibility, or MCP-style access.</p></td></tr><tr><td><p>Will this API still work after the prototype stage?</p></td><td><p>A provider can be easy to try and still be hard to build around.</p></td></tr></tbody></table>

<h2 id="heading-final-thoughts"><strong>Final Thoughts</strong></h2>
<p>A good stock API should reduce project risk, not just return data.</p>
<p>If you're building a small chart, almost any clean price endpoint can work. But once the same API starts supporting a backtester, screener, dashboard, valuation tool, or AI assistant, the decision becomes more important. The provider affects your data quality, parsing logic, refresh jobs, licensing choices, and future product direction.</p>
<p>This is why workflow fit matters more than endpoint count. For projects that need several layers together, such as real-time and historical market data, fundamentals, indicators, developer-friendly access, spreadsheet support, and MCP-style AI workflows, Alpha Vantage fits well. For narrower workflow needs, another provider may make more sense.</p>
<p>Choose the API as part of your project’s data infrastructure, not just as a list of endpoints.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Design APIs for AI Agents ]]>
                </title>
                <description>
                    <![CDATA[ APIs are designed for human developers. People read documentation, infer the intent behind an endpoint, and know how to handle edge cases when something unexpected happens. AI agents don't have that c ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-design-apis-for-ai-agents/</link>
                <guid isPermaLink="false">6a18bdb078258754833f8205</guid>
                
                    <category>
                        <![CDATA[ llm ]]>
                    </category>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ai-agent ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ David Aniebo ]]>
                </dc:creator>
                <pubDate>Thu, 28 May 2026 22:12:00 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/056b20d6-7409-4b6e-a29c-0b48061a7508.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>APIs are designed for human developers. People read documentation, infer the intent behind an endpoint, and know how to handle edge cases when something unexpected happens.</p>
<p>AI agents don't have that context and understanding.</p>
<p>AI agent understand APIs through schemas, examples, randomized data and live responses. When a behavior or method is ambiguous and inconsistent, the model doesn't pause to “think” – it fills in the blanks (randomizing).</p>
<p>In production, those guesses could become blocks, retry storms, duplicated side effects, or broken workflows.</p>
<p>This is why APIs that are perfectly fine for humans frequently fail under AI agent use. The problem is rarely “the agent isn’t smart enough.” More often, the API was never designed for an agent/machine consumer that must plan, call tools, and recover from failure without a human in the loop.</p>
<p>In this guide, you’ll learn how to design APIs that agents can use reliably. We’ll anchor the discussion in three practical ideas:</p>
<ol>
<li><p><strong>Deterministic behavior:</strong> same inputs and state should yield predictable outcomes and shapes.</p>
</li>
<li><p><strong>Strong schemas:</strong> contracts that are complete, descriptive, and testable.</p>
</li>
<li><p><strong>Guardrails at the API boundary:</strong> authorization, validation, and safe defaults that prevent unsafe autonomy.</p>
</li>
</ol>
<p>The aim of this article is not to build “AI-powered” APIs, but rather to build APIs that are <strong>clear, strict,</strong> and <strong>dependable,</strong> even when the caller is not an agent but a fellow developers leveraging various tools.</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-why-good-enough-for-devs-is-not-good-enough-for-agents">Why “Good Enough for Devs” Is Not Good Enough for Agents</a></p>
</li>
<li><p><a href="#heading-principle-1-deterministic-behavior">Principle 1: Deterministic Behavior</a></p>
</li>
<li><p><a href="#heading-principle-2-strong-schemas">Principle 2: Strong Schemas</a></p>
</li>
<li><p><a href="#heading-principle-3-guardrails-at-the-api-boundary">Principle 3: Guardrails at the API Boundary</a></p>
</li>
<li><p><a href="#heading-patterns-that-bridge-apis-and-agent-runtimes">Patterns That Bridge APIs and Agent Runtimes</a></p>
</li>
<li><p><a href="#heading-a-practical-before-and-after-example">A Practical Before and After Example</a></p>
</li>
<li><p><a href="#heading-checklist-is-your-api-agent-ready">Checklist: Is Your API Agent-Ready?</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before reading this guide, it helps to have:</p>
<ul>
<li><p>A basic understanding of HTTP APIs and REST concepts</p>
</li>
<li><p>Familiarity with JSON and API request/response patterns</p>
</li>
<li><p>An understanding of common API concepts like authentication, pagination, and retries</p>
</li>
</ul>
<h2 id="heading-why-good-enough-for-devs-is-not-good-enough-for-agents">Why “Good Enough for Devs” Is Not Good Enough for Agents</h2>
<p>Human developers bring implied and contextual knowledge: they read through Slack threads, read blog posts, and recognize that “this 404 usually means you forgot the workspace ID.”</p>
<p>Agents mostly get whatever is in the spec, the examples, and the last response body.</p>
<p>That gap shows up in predictable ways:</p>
<ul>
<li><p><strong>Ambiguous semantics:</strong> wrong endpoint or wrong parameter combination.</p>
</li>
<li><p><strong>Undocumented branches:</strong> the model invents fields or misreads optional behavior.</p>
</li>
<li><p><strong>Inconsistent error bodies:</strong> retries that shouldn't happen, or no retry when one is safe.</p>
</li>
<li><p><strong>Non-idempotent “do things” endpoints:</strong> duplicate charges, duplicate tickets, duplicate emails.</p>
</li>
</ul>
<p>Industry commentary and practitioner guides converge on the same point: agents are becoming a major class of API consumer, and machine legibility matters as much as developer experience.</p>
<p>See for example discussions of OpenAPI as the source of truth for agents, emerging tool protocols, and traffic patterns that differ from human clients in the resources listed at the end of this article.</p>
<h2 id="heading-principle-1-deterministic-behavior">Principle 1: Deterministic Behavior</h2>
<p>Determinism for agents doesn't mean “always return the same JSON forever.” It means: <strong>given the same request and the same server-side state, your API behaves in a way the agent can model</strong> and when state changes, you make that explicit.</p>
<h3 id="heading-prefer-explicit-state-over-hidden-magic">Prefer Explicit State Over Hidden Magic</h3>
<p>Agents struggle with “sometimes the server does X depending on internal flags.” Where humans infer intent from product copy, agents infer from patterns. If those patterns drift, autonomy breaks.</p>
<p>Practical habits:</p>
<ul>
<li><p>Model lifecycle explicitly (<code>draft</code> → <code>submitted</code> → <code>approved</code>) instead of overloading a single <code>status</code> field with undocumented combinations.</p>
</li>
<li><p>Return what changed after mutations (updated resource, relevant IDs, next allowed actions).</p>
</li>
<li><p>Avoid silent coercion (auto-correcting bad enums, silently dropping unknown fields) unless you document and signal it.</p>
</li>
</ul>
<h3 id="heading-make-writes-safe-idempotency-and-intent-keys">Make Writes Safe: Idempotency and Intent Keys</h3>
<p>For any endpoint that bills, sends messages, provisions infrastructure, or otherwise <strong>does something irreversible</strong>, assume double-submission will happen.</p>
<ul>
<li><p>Support idempotency keys (header or body) for create-like operations.</p>
</li>
<li><p>Use clear HTTP semantics: <code>POST</code> creates, <code>PUT</code> replaces where appropriate, <code>PATCH</code> for partial updates and document what repeats mean.</p>
</li>
<li><p>Where duplicates are possible, offer a lookup-by-client-reference path so agents can reconcile.</p>
</li>
</ul>
<h3 id="heading-pagination-and-sorting-one-pattern-everywhere">Pagination and Sorting: One Pattern, Everywhere</h3>
<p>Agents loop. If every resource paginates differently, the model will mix strategies.</p>
<p>To combat this, pick one pagination style (cursor vs offset) per API surface and stick to it.</p>
<p>Also, always return stable sort order or require <code>sort</code> explicitly. You should also include <code>next</code> links or cursors in a consistent envelope.</p>
<h3 id="heading-timeouts-partial-success-and-async-work">Timeouts, Partial Success, and Async Work</h3>
<p>Agents hate “maybe it worked.” Long-running work should be <strong>explicitly async</strong>:</p>
<ul>
<li><p><code>202 Accepted</code> + job ID + polling or webhooks.</p>
</li>
<li><p>Clear terminal states: <code>succeeded</code>, <code>failed</code>, <code>canceled</code>, with structured error details on failure.</p>
</li>
</ul>
<h2 id="heading-principle-2-strong-schemas">Principle 2: Strong Schemas</h2>
<p>If determinism is about behavior, schemas are about communication. For agents, your OpenAPI (or equivalent) isn't paperwork, it's part of the runtime interface.</p>
<h3 id="heading-treat-openapi-as-a-contract-not-a-souvenir">Treat OpenAPI as a Contract, Not a Souvenir</h3>
<p>A specification that lags production is worse than no spec: it trains the agent to be confidently wrong. Teams increasingly treat OpenAPI as the authoritative contract and validate requests/responses against it in CI and at the edge.</p>
<p>Here's the minimum bar for agent-friendly OpenAPI:</p>
<ul>
<li><p>Every operation has a <code>summary</code> and a <code>description</code> that explain <em>when</em> to use it, not only <em>what</em> it returns.</p>
</li>
<li><p>Every request body property has <code>description</code> and realistic <code>example</code> values.</p>
</li>
<li><p>All responses are documented including 4xx/5xx with stable JSON shapes.</p>
</li>
</ul>
<h3 id="heading-describe-intent-in-natural-language-precisely">Describe Intent in Natural Language, Precisely</h3>
<p>Agents aren't offended by verbosity. They're confused by vague verbs.</p>
<p>Instead of:</p>
<blockquote>
<p>“Gets orders.”</p>
</blockquote>
<p>Prefer:</p>
<blockquote>
<p>“Lists orders for the authenticated merchant. Supports filtering by <code>status</code> and a time window on <code>created_at</code>. Returns at most <code>limit</code> items; use <code>cursor</code> for the next page.”</p>
</blockquote>
<p>This aligns with what multiple guides call <strong>context-aware</strong> or <strong>self-describing</strong> APIs: the schema carries semantic intent, not just types.</p>
<h3 id="heading-examples-are-part-of-the-contract">Examples Are Part of the Contract</h3>
<p>You should provide a happy path example per endpoint, at least one validation error example (400) with your standard error object, and examples for optional fields when they change behavior.</p>
<p>Examples reduce “shape hallucination” where the model guesses field names or nesting.</p>
<h3 id="heading-json-schema-strictness-helps-tool-calling-stacks">JSON Schema Strictness Helps Tool-Calling Stacks</h3>
<p>If your agent uses function calling / structured outputs, tighten schemas:</p>
<ul>
<li><p>Prefer <code>enum</code> for small closed sets.</p>
</li>
<li><p>Mark fields <code>required</code> honestly.</p>
</li>
<li><p>Use <code>format</code> (<code>uuid</code>, <code>date-time</code>) where real.</p>
</li>
<li><p>Avoid <code>additionalProperties: true</code> on security-sensitive payloads if you need strict validation.</p>
</li>
</ul>
<h3 id="heading-name-things-consistently">Name Things Consistently</h3>
<p><code>userId</code> in one endpoint and <code>user_id</code> in another is a human annoyance and an agent trap. Pick a convention and enforce it.</p>
<h2 id="heading-principle-3-guardrails-at-the-api-boundary">Principle 3: Guardrails at the API Boundary</h2>
<p>Autonomy amplifies mistakes. Guardrails turn “oops” into blocked requests instead of incidents.</p>
<h3 id="heading-authorization-should-be-narrow-and-explicit">Authorization Should Be Narrow and Explicit</h3>
<p>Agents should receive credentials scoped to <strong>least privilege</strong>. For example, use short-lived tokens, with refresh documented clearly. Use scopes that map to real actions (<code>orders:read</code> vs <code>orders:write</code>). And avoid flows that assume a human can solve (CAPTCHAs) or click (email links mid-run) or isolate those as human-in-the-loop tools.</p>
<h3 id="heading-validate-hard-fail-loud-and-structured">Validate Hard, Fail Loud and Structured</h3>
<p>Reject bad input at the edge with stable <code>error_code</code> values (machine-actionable), human-readable <code>message</code> (for logs and UI), optional <code>field</code> or JSON Pointer to the problem, and optional <code>doc_url</code> linking to documentation.</p>
<p>This matches guidance from several practitioner articles: opaque 500s and generic errors are where autonomous clients spiral.</p>
<p>RFC 7807 Problem Details (<code>application/problem+json</code>) is a good, widely understood pattern for HTTP APIs, a structured envelope agents can parse consistently.</p>
<h3 id="heading-separate-read-the-world-from-change-the-world">Separate “Read the World” from “Change the World”</h3>
<p>For high-impact actions (refunds, deletes, transfers), consider using a two-step pattern: first create an intent, then confirm execution.</p>
<p>Or you can dry-run query parameters / dedicated endpoints that validate without committing.</p>
<p>Also keep in mind that rate limits and quotas tuned for bursty agent behavior and autonomous loops can dwarf human traffic.</p>
<h3 id="heading-observability-is-a-product-feature">Observability is a Product Feature</h3>
<p>Log correlation IDs, surface them in responses where safe, and monitor for retry amplification. An agent that misreads a 409 as “retry forever” becomes a denial-of-wallet attack on your own systems.</p>
<h2 id="heading-patterns-that-bridge-apis-and-agent-runtimes">Patterns That Bridge APIs and Agent Runtimes</h2>
<h3 id="heading-workflow-documentation-sequences-not-just-endpoints">Workflow Documentation: Sequences, Not Just Endpoints</h3>
<p>Agents excel when they can follow a recipe. Document common sequences (“create customer → add payment method → charge”) and consider standards meant for multi-step API flows (such as Arazzo) when your product’s complexity justifies it.</p>
<h3 id="heading-hypermedia-and-next-steps">Hypermedia and “Next Steps”</h3>
<p>Including links to plausible next actions (for example, pagination <code>next</code>, or related resources) reduces improvisation. This is the same spirit as <a href="https://en.wikipedia.org/wiki/HATEOAS">HATEOAS</a>: the response whispers what you can do next, instead of forcing the model to guess URLs.</p>
<h3 id="heading-tool-oriented-surfaces-for-example-mcp">Tool-Oriented Surfaces (For Example, MCP)</h3>
<p>Protocols like the Model Context Protocol (MCP) are gaining traction as a way to expose curated capabilities (“tools”) with schemas agents can bind to directly.</p>
<p>A common pragmatic pattern is not to dump every micro-endpoint as a tool, but to expose coarse-grained tools aligned to user outcomes while keeping your underlying REST API strict and clean.</p>
<p>MCP isn't a substitute for good API design. It's a delivery and discovery layer. Slapping a thin wrapper on a messy API still leaves you with a messy system – it just fails faster in public.</p>
<h3 id="heading-metadata-for-discovery-llmstxt-and-friends">Metadata for Discovery (<code>llms.txt</code> and Friends)</h3>
<p>Some teams publish <code>/llms.txt</code> or similar lightweight discovery files for documentation sites. Treat these as optional signposts, not replacements for OpenAPI.</p>
<p>Ecosystem adoption is still evolving, but the underlying idea is sound: make the canonical machine-readable description easy to find.</p>
<h2 id="heading-a-practical-beforeafter">A Practical Before/After</h2>
<h3 id="heading-weak-pattern-agent-hostile">Weak Pattern (Agent-hostile)</h3>
<pre><code class="language-http">POST /do-stuff
</code></pre>
<p>Response <code>200 OK</code>:</p>
<pre><code class="language-json">{ "ok": true }
</code></pre>
<p>Problems: no idempotency, no structured error, no entity ID, no way to poll, the agent must guess whether “ok” means “created” or “ignored duplicate.”</p>
<h3 id="heading-stronger-pattern-agent-friendly">Stronger Pattern (Agent-friendly)</h3>
<pre><code class="language-http">POST /v1/invoices
Idempotency-Key: 7b3c-...
</code></pre>
<p>Response <code>201 Created</code>:</p>
<pre><code class="language-json">{
  "invoice": {
    "id": "inv_9Qz",
    "status": "draft",
    "total": { "amount": "120.00", "currency": "USD" }
  },
  "links": {
    "finalize": "/v1/invoices/inv_9Qz/finalize"
  }
}
</code></pre>
<p>Conflict response <code>409 Conflict</code> with Problem Details:</p>
<pre><code class="language-json">{
  "type": "https://api.example.com/problems/duplicate-idempotency-key",
  "title": "Duplicate idempotency key",
  "status": 409,
  "detail": "A different request body was sent with the same Idempotency-Key.",
  "error_code": "IDEMPOTENCY_KEY_REUSE_BODY_MISMATCH"
}
</code></pre>
<p>This tells the agent what happened and whether retrying is appropriate.</p>
<h2 id="heading-checklist-is-your-api-agent-ready">Checklist: Is Your API Agent-Ready?</h2>
<ul>
<li><p><strong>Contract</strong>: Published OpenAPI 3.x, validated against real traffic, with rich descriptions and examples.</p>
</li>
<li><p><strong>Determinism</strong>: Documented state machines, consistent pagination, explicit async for long jobs.</p>
</li>
<li><p><strong>Safe writes</strong>: Idempotency for side effects, reconciliation endpoints where needed.</p>
</li>
<li><p><strong>Errors</strong>: Stable codes, structured bodies, documented remediation paths.</p>
</li>
<li><p><strong>Security</strong>: Least-privilege tokens, no “mystery” side doors agents can accidentally hit.</p>
</li>
<li><p><strong>Operations</strong>: Rate limits, bulk endpoints where appropriate, correlation IDs, dashboards for anomalous agent traffic.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Designing for AI agents is, in most respects, disciplined API design — pushed to the level where machines can rely on your contract without tribal knowledge.</p>
<p>If you remember only three things:</p>
<ol>
<li><p><strong>Be predictable:</strong> in shapes, states, and side effects.</p>
</li>
<li><p><strong>Be explicit:</strong> in schemas, examples, and errors.</p>
</li>
<li><p><strong>Be protective:</strong> validate early, scope narrowly, and make dangerous actions hard to trigger by accident.</p>
</li>
</ol>
 ]]>
                </content:encoded>
            </item>
        
            <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>
        
    </channel>
</rss>
