<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: George Arthur</title>
    <description>The latest articles on DEV Community by George Arthur (@malvinjay).</description>
    <link>https://dev.to/malvinjay</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F244887%2F44b6a630-c596-49ce-bb0a-662bf7b790ef.jpg</url>
      <title>DEV Community: George Arthur</title>
      <link>https://dev.to/malvinjay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/malvinjay"/>
    <language>en</language>
    <item>
      <title>The $26 Billion Leak: Why Frameworks Are Making Your Codebase Rot (and How Hexagonal Isolation Fixes It)</title>
      <dc:creator>George Arthur</dc:creator>
      <pubDate>Fri, 31 Jul 2026 00:48:05 +0000</pubDate>
      <link>https://dev.to/malvinjay/the-26-billion-leak-why-frameworks-are-making-your-codebase-rot-and-how-hexagonal-isolation-ian</link>
      <guid>https://dev.to/malvinjay/the-26-billion-leak-why-frameworks-are-making-your-codebase-rot-and-how-hexagonal-isolation-ian</guid>
      <description>&lt;p&gt;According to a 2024 vFunction survey, over 50% of companies watch more than a quarter of their entire IT budget vanish into technical debt.&lt;/p&gt;

&lt;p&gt;This isn't happening because developers are bad at writing code. It's happening because our architectural boundaries are eroding. We're letting business rules bleed into database models, and HTTP controllers bleed into core logic.&lt;/p&gt;

&lt;p&gt;The primary culprit? Modern framework marketing and the "tutorial culture" that dictates how we learn to code.&lt;/p&gt;

&lt;p&gt;Here's a deep look into why your architecture is bleeding, the engineering principles we're violating, and how Hexagonal Isolation offers a structural cure — with a full working TypeScript codebase, &lt;a href="https://github.com/MalvinJay/FlowBank" rel="noopener noreferrer"&gt;FlowBank&lt;/a&gt;, so you can see it applied rather than just described.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Trap: "Time-to-Hello-World" vs. Architectural Debt
&lt;/h3&gt;

&lt;p&gt;Most developers learn to build applications through 15-minute video tutorials or framework quickstarts. To win adoption, frameworks optimize for rapid onboarding — showing you how to build a Todo app in three steps using "magic" shortcuts.&lt;/p&gt;

&lt;p&gt;This creates three architectural traps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Active Record Trap&lt;/strong&gt; — ORMs couple database schemas directly to business entities. Change your table, and your core business model breaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Controller-to-DB Shortcut&lt;/strong&gt; — Framework boilerplate encourages writing validation, business rules, and SQL directly inside HTTP handlers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The "Magic" Annotation Bleed&lt;/strong&gt; — Framework-specific decorators injected straight into domain objects make the entire application hostage to that vendor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Follow these shortcuts long enough and you accumulate &lt;strong&gt;Architectural Technical Debt (ATD)&lt;/strong&gt;. Three years later, upgrading your framework or switching database providers means rewriting the foundational business rules of the company.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Solution: What Is &lt;strong&gt;Hexagonal Isolation&lt;/strong&gt;?
&lt;/h3&gt;

&lt;p&gt;Originally coined by &lt;a href="https://alistaircockburn.com/" rel="noopener noreferrer"&gt;Alistair Cockburn&lt;/a&gt; as &lt;strong&gt;Ports&lt;/strong&gt; and &lt;strong&gt;Adapters&lt;/strong&gt;, Hexagonal Architecture solves layer bleed by enforcing strict structural isolation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[External World] ----&amp;gt; ( Port / Interface ) ----&amp;gt; [Core Business Logic]
(HTTP/DB/CLI)          (Strict Abstraction)       (Pure Domain Rules)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The Core Domain — Contains nothing but pure business logic. Zero knowledge of the web, databases, file systems, or frameworks.&lt;/li&gt;
&lt;li&gt;Ports (Abstract Interfaces) — The hexagon's edge is built entirely of abstract interfaces. The core only talks to these.&lt;/li&gt;
&lt;li&gt;Adapters (Concrete Implementations) — Postgres, REST APIs, GraphQL, S3 — all live outside the hexagon, implementing the ports to feed data in and out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By isolating the core through abstraction, you get true plug-and-play architecture. Swap MongoDB for PostgreSQL without touching a single line of your core application rules.&lt;/p&gt;

&lt;h4&gt;
  
  
  A minimal TypeScript example
&lt;/h4&gt;

&lt;p&gt;This is a trimmed-down version of the pattern used throughout &lt;a href="https://github.com/MalvinJay/FlowBank" rel="noopener noreferrer"&gt;FlowBank&lt;/a&gt;, a small open-source banking API built specifically to demonstrate this architecture end to end — transfers, deposits, notifications, and payment processing, all with the core fully isolated from Postgres, Stripe, and Express.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// core/ports/user-repository.port.ts&lt;/span&gt;
&lt;span class="c1"&gt;// The PORT — an abstract interface the core depends on, not a concrete DB.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// core/domain/user.ts&lt;/span&gt;
&lt;span class="c1"&gt;// The CORE — pure business logic, no framework or DB imports.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;changeEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;newEmail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newEmail&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// adapters/postgres-user-repository.ts&lt;/span&gt;
&lt;span class="c1"&gt;// The ADAPTER — concrete implementation, swappable without touching the core.&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserRepository&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../core/ports/user-repository.port&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../core/domain/user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./db-client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PostgresUserRepository&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;UserRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT * FROM users WHERE id = $1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE users SET email = $1 WHERE id = $2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// simplified for brevity&lt;/span&gt;
      &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;User&lt;/code&gt; class and its business rules never import &lt;code&gt;db-client&lt;/code&gt;, Express, or anything Postgres-specific. Tomorrow, swapping &lt;code&gt;PostgresUserRepository&lt;/code&gt; for a &lt;code&gt;MongoUserRepository&lt;/code&gt; or an &lt;code&gt;InMemoryUserRepository&lt;/code&gt; (for tests) requires zero changes to the core.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Theoretical Pillars: &lt;strong&gt;SOLID &amp;amp; DDD&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Hexagonal isolation isn't an arbitrary design choice — it's the concrete implementation of foundational computer science principles.&lt;/p&gt;

&lt;h4&gt;
  
  
  The SOLID matrix:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Inversion Principle (DIP)&lt;/strong&gt; — High-level business logic must not depend on low-level frameworks; both depend on abstractions. Hexagonal architecture enforces that all source-code dependencies point inward, toward the core.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single Responsibility Principle (SRP)&lt;/strong&gt; — Domain models gain a single reason to change: a change in business requirements. They're stripped of framework plumbing entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open/Closed Principle (OCP)&lt;/strong&gt; — Open for extension, closed for modification. Need a new SMS provider? Write a new adapter. The core stays untouched.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Domain-Driven Design &amp;amp; component coupling:
&lt;/h4&gt;

&lt;p&gt;Hexagonal isolation belongs to the same family as Clean Architecture (Robert C. Martin) and Onion Architecture (Jeffrey Palermo). It enforces the Acyclic Dependencies Principle (ADP), eliminating circular references, and honors the Stable Dependencies Principle (SDP) by making the most stable component — your business rules — the target of all dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The Engineering Payoff
&lt;/h3&gt;

&lt;p&gt;Implementing hexagonal isolation means more boilerplate upfront, but the long-term ROI is substantial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bulletproof testability&lt;/strong&gt; — Since the core only depends on abstract ports, you can mock databases and external APIs instantly. Test your entire business logic in milliseconds, no Docker containers or live servers required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework agnosticism&lt;/strong&gt; — Your framework becomes a delivery mechanism, not the definition of your application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Future-proof evolution&lt;/strong&gt; — When a faster database or cheaper cloud provider shows up, migration cost drops from a full rewrite to building a single new adapter.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. See It in a Real Codebase: &lt;a href="https://github.com/MalvinJay/FlowBank" rel="noopener noreferrer"&gt;FlowBank&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Reading about a pattern and watching it hold up under a real dependency graph are two different experiences. That's why this article ships with FlowBank — an open-source TypeScript banking API built to demonstrate hexagonal architecture through actual money-movement workflows: transfers, deposits, notifications, and payment processing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
  core/          &amp;lt;- the hexagon's interior, zero framework imports allowed
    domain/         Account, Money, Transaction — pure business rules
    ports/           interfaces the core depends on, never concretes
    use-cases/       application logic, orchestrates domain + ports
    errors/          domain-specific error types

  adapters/       &amp;lt;- everything outside the hexagon
    postgres/        real DB implementation of the repository ports
    stripe/          real payment gateway implementation
    email/           real notification implementation
    http/            Express router — a driving adapter, calls IN to use cases
    in-memory/       fakes used by tests, swap-in for Postgres/Stripe/email

  main.ts         &amp;lt;- composition root: the ONLY file allowed to wire
                     concrete adapters into the use cases
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of things worth actually trying rather than just reading about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;npm test&lt;/code&gt; runs the core's business-rule tests with no database, no network call, and no Docker container — just the in-memory adapters standing in for Postgres and Stripe.&lt;/li&gt;
&lt;li&gt;Grep the codebase for proof, not promises: &lt;code&gt;grep -rl "from.*adapters" src/core&lt;/code&gt; returns nothing. The dependency arrows in the diagram earlier in this article aren't aspirational — they're enforced by what actually got imported.&lt;/li&gt;
&lt;li&gt;Try swapping &lt;code&gt;StripePaymentGateway&lt;/code&gt; for a hypothetical &lt;code&gt;PaystackPaymentGateway&lt;/code&gt;, or &lt;code&gt;PostgresAccountRepository&lt;/code&gt; for a Mongo equivalent. Neither touches &lt;code&gt;Account&lt;/code&gt;, &lt;code&gt;Money&lt;/code&gt;, or the use cases — which is the entire argument of this article, made concrete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Clone it&lt;/strong&gt;, &lt;strong&gt;break it&lt;/strong&gt;, &lt;strong&gt;extend it&lt;/strong&gt;. The repo is the proof that this isn't just a diagram — it's a codebase that ships, tests fast, and survives a provider swap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Technical debt doesn't usually arrive as one bad decision — it accumulates one framework shortcut at a time, until the business logic and the delivery mechanism are so entangled that neither can change without the other breaking. Hexagonal isolation is a direct, structural answer to that: push every framework, database, and vendor SDK to the edges, and keep a small, dependency-free core that only knows about the business it serves.&lt;/p&gt;

&lt;p&gt;The payoff isn't theoretical. It shows up as tests that run in milliseconds instead of minutes, migrations that take a sprint instead of a quarter, and a codebase new engineers can actually reason about because the business rules aren't buried inside ORM hooks and route handlers.&lt;/p&gt;

&lt;p&gt;Stop building software like a 15-minute tutorial. Isolate your core, protect your boundaries, clone &lt;a href="https://github.com/MalvinJay/FlowBank" rel="noopener noreferrer"&gt;FlowBank&lt;/a&gt; and see the pattern for yourself, and claw back your engineering budget.&lt;/p&gt;

&lt;h4&gt;
  
  
  #HappyCoding
&lt;/h4&gt;

&lt;h4&gt;
  
  
  #BuildScalableSystems
&lt;/h4&gt;

</description>
      <category>architecture</category>
      <category>softwareengineering</category>
      <category>solidprinciples</category>
      <category>cleanarchitecture</category>
    </item>
    <item>
      <title>When a Successful Payment Still Couldn't Schedule a Zoom Meeting</title>
      <dc:creator>George Arthur</dc:creator>
      <pubDate>Fri, 31 Jul 2026 00:38:37 +0000</pubDate>
      <link>https://dev.to/malvinjay/when-a-successful-payment-still-couldnt-schedule-a-zoom-meeting-12fa</link>
      <guid>https://dev.to/malvinjay/when-a-successful-payment-still-couldnt-schedule-a-zoom-meeting-12fa</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/devteam/devs-big-summer-bug-smash-is-now-live-share-5000-in-cash-prizes-skateboards-and-more-across-57mk?bb=263840"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A payment webhook sounds simple until a successful payment doesn't actually result in the service the customer paid for.&lt;/p&gt;

&lt;p&gt;That was one of the more interesting bugs I encountered while building The Listening Ear, an appointment and online consultation platform.&lt;/p&gt;

&lt;p&gt;The requirement was straightforward:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A customer pays for a session → the application confirms the payment → the customer's appointment is booked → a Zoom meeting is created.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reality was much more complicated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Project Overview
&lt;/h3&gt;

&lt;p&gt;The Listening Ear connects online payments with appointment scheduling and Zoom-based consultations.&lt;/p&gt;

&lt;p&gt;The application was built with technologies including Next.js 14, TypeScript, Supabase, Prisma, PostgreSQL, Zoom, and payment-provider APIs.&lt;/p&gt;

&lt;p&gt;The payment workflow was particularly important because payment confirmation was effectively the gatekeeper for the rest of the booking experience.&lt;/p&gt;

&lt;p&gt;The intended flow looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer
   │
   ▼
Payment Provider
   │
   │ webhook
   ▼
Next.js Webhook
   │
   ├── Verify / interpret payment
   │
   ├── Create Zoom meeting
   │
   └── Create appointment record
   │
   ▼
Customer receives access to their scheduled session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem was that the webhook sat directly in the middle of all of these operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h3&gt;

&lt;p&gt;The bug appeared when I was implementing the payment webhook that would unlock the Zoom scheduling workflow.&lt;/p&gt;

&lt;p&gt;My initial implementation listened for the payment event and checked whether the event was:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;if (event === 'charge.success') {&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once that condition was met, the webhook immediately continued into the booking workflow.&lt;/p&gt;

&lt;p&gt;That workflow included:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reading appointment metadata from the payment event.&lt;/li&gt;
&lt;li&gt;Handling special emergency appointments.&lt;/li&gt;
&lt;li&gt;Building the Zoom meeting payload.&lt;/li&gt;
&lt;li&gt;Calling the Zoom meeting API.&lt;/li&gt;
&lt;li&gt;Creating the appointment record in the database.&lt;/li&gt;
&lt;li&gt;Returning a successful response to the payment provider.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The problem was that all of these operations were effectively coupled to the webhook request.&lt;/p&gt;

&lt;p&gt;A payment webhook is an asynchronous external event. It can be retried, delivered more than once, or arrive while another part of the system is still processing.&lt;/p&gt;

&lt;p&gt;My implementation initially treated it too much like a normal synchronous API request.&lt;/p&gt;

&lt;p&gt;That created a bottleneck:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Payment confirmation → webhook → Zoom API → appointments API → response&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If one of those downstream operations was slow or failed, the webhook itself could fail.&lt;/p&gt;

&lt;p&gt;That meant the payment provider could retry the webhook.&lt;/p&gt;

&lt;p&gt;And then I could potentially end up processing the same successful payment again.&lt;/p&gt;

&lt;p&gt;That was the real bug I needed to solve.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Debugging Journey
&lt;/h3&gt;

&lt;p&gt;The first symptom was easy to misinterpret.&lt;/p&gt;

&lt;p&gt;The user had successfully completed payment, but the expected Zoom scheduling experience wasn't always completing correctly.&lt;/p&gt;

&lt;p&gt;My first instinct was naturally to look at the Zoom integration.&lt;/p&gt;

&lt;p&gt;But following the request backwards exposed a more interesting dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Zoom scheduling
      ▲
      │
Appointment creation
      ▲
      │
Payment webhook
      ▲
      │
Payment provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Zoom API wasn't necessarily the original problem.&lt;/p&gt;

&lt;p&gt;The payment webhook was responsible for too much.&lt;/p&gt;

&lt;p&gt;I started tracing the webhook from the incoming payment event through each downstream operation.&lt;/p&gt;

&lt;p&gt;The relevant portion of the original implementation was essentially:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;charge.success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/create-zoom-meeting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;zoomPayload&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bookedSlotRes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/appointments&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bookingInfo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;appointmentDay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bookingInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;bookingInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;transaction_ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;tx_ref&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
                    &lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bookingInfo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;sessionType&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Zoom meeting created with booked time slot&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;bookedSlot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bookedSlotRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;meeting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;meeting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was the turning point in my investigation.&lt;/p&gt;

&lt;p&gt;The webhook wasn't merely acknowledging a payment.&lt;/p&gt;

&lt;p&gt;It was also acting as an orchestration layer for two additional services.&lt;/p&gt;

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

&lt;p&gt;This is where the issue became particularly interesting.&lt;/p&gt;

&lt;p&gt;The webhook endpoint only returned a successful response after the Zoom request and appointment request had completed.&lt;/p&gt;

&lt;p&gt;So the lifecycle was effectively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment Provider
      │
      │ charge.success
      ▼
Webhook
      │
      ├──────► Zoom API
      │          │
      │          ▼
      │       Success?
      │
      ├──────► Appointments API
      │          │
      │          ▼
      │       Success?
      │
      ▼
  HTTP 200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If something went wrong before the final response, the handler entered the catch block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Webhook Error:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error occured handling webhook&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the application's perspective, that was a reasonable error response.&lt;/p&gt;

&lt;p&gt;From the perspective of a payment provider delivering a webhook, however, a 500 can mean:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The receiving system didn't successfully process this event. Try again."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that is exactly where retries become dangerous.&lt;/p&gt;

&lt;p&gt;A retry could cause the application to attempt to create the Zoom meeting again.&lt;/p&gt;

&lt;p&gt;That introduced the possibility of duplicate downstream side effects from a single payment event.&lt;/p&gt;

&lt;h3&gt;
  
  
  There Was Another Problem
&lt;/h3&gt;

&lt;p&gt;While debugging the webhook, I also revisited the signature-verification portion of the implementation.&lt;/p&gt;

&lt;p&gt;The original code contained this:&lt;br&gt;
&lt;code&gt;const stringifiedBody = request.body?.toString() as string;&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha512&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stringifiedBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then compared the generated hash against the Paystack signature:&lt;br&gt;
&lt;code&gt;request.headers.get('x-paystack-signature')&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;However, the request body had already been consumed here:&lt;br&gt;
&lt;code&gt;const jsonData = await request.json();&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That made the raw request-body handling problematic.&lt;/p&gt;

&lt;p&gt;More importantly, the actual signature check had been left commented out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// if (hash === request.headers.get('x-paystack-signature')) {&lt;/span&gt;
&lt;span class="c1"&gt;// }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was an important reminder that webhook reliability isn't only about successfully processing an event.&lt;/p&gt;

&lt;p&gt;There are two separate questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is this event authentic?&lt;/li&gt;
&lt;li&gt;Can my application process it safely and reliably?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;I reworked the webhook flow around a much clearer separation of responsibilities.&lt;br&gt;
The first principle was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A payment webhook should establish payment state reliably before downstream services act on it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rather than treating &lt;code&gt;charge.success&lt;/code&gt; as permission to blindly execute the entire booking workflow, I treated the event as a state transition that needed to be interpreted and handled carefully.&lt;/p&gt;

&lt;p&gt;The booking metadata continued to provide the information required for the session:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const bookingInfo: any = jsonData?.data?.metadata;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The implementation also had to account for special appointment types.&lt;/p&gt;

&lt;p&gt;For example, emergency appointments required the application to calculate a new time range:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookingInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appointmentOption&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;emergency&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nf"&gt;computeTimeRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookingInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessionType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;startTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;bookingInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;bookingInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That information then became part of the Zoom meeting payload.&lt;/p&gt;

&lt;p&gt;The important change was not simply changing one if statement.&lt;/p&gt;

&lt;p&gt;It was making the payment → booking → Zoom relationship explicit and predictable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making Webhook Processing Safer
&lt;/h3&gt;

&lt;p&gt;The biggest lesson was that webhook handlers should be designed with retries in mind from the beginning.&lt;/p&gt;

&lt;p&gt;I had to think about questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happens if the same successful payment event arrives twice?&lt;/li&gt;
&lt;li&gt;What happens if Zoom succeeds but appointment creation fails?&lt;/li&gt;
&lt;li&gt;What happens if appointment creation succeeds but the webhook response fails?&lt;/li&gt;
&lt;li&gt;What happens if the provider retries an event after a timeout?&lt;/li&gt;
&lt;li&gt;How do I prevent duplicate meetings?&lt;/li&gt;
&lt;li&gt;How do I prevent duplicate appointment records?&lt;/li&gt;
&lt;li&gt;When is it actually safe to tell the payment provider that processing succeeded?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't unusual edge cases for webhook-driven systems.&lt;/p&gt;

&lt;p&gt;They are part of the normal operating environment.&lt;/p&gt;

&lt;p&gt;The resulting approach was therefore much more deliberate about payment state, downstream operations, and retry behavior rather than treating the webhook as a one-shot function.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Zoom Integration Was Only Half the Story
&lt;/h3&gt;

&lt;p&gt;One of the most useful debugging lessons came from separating the visible symptom from the actual source of the problem.&lt;/p&gt;

&lt;p&gt;The user experience looked like a Zoom problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I paid, but I can't get my meeting."&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;But the actual chain was:

Payment
   ↓
Webhook
   ↓
Payment state
   ↓
Booking
   ↓
Zoom meeting

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

&lt;/div&gt;



&lt;p&gt;That meant I couldn't reliably fix the issue by staring only at the Zoom integration.&lt;/p&gt;

&lt;p&gt;The payment webhook was the system boundary where the external payment provider's state became internal application state.&lt;/p&gt;

&lt;p&gt;Once that state was handled correctly, the rest of the workflow became much easier to reason about.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Small Detail That Also Caught Me
&lt;/h3&gt;

&lt;p&gt;The webhook also handled optional invitees for Zoom meetings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookingInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invites&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;bookingInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invites&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsedInvites&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bookingInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;parsedInvites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;invitee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;zoomPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meeting_invitees&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invitee&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="nx"&gt;zoomPayload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authentication_exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invitee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invitee&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was another example of why webhook handlers can become deceptively complicated.&lt;/p&gt;

&lt;p&gt;A single payment event was carrying metadata that influenced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;appointment timing;&lt;/li&gt;
&lt;li&gt;emergency-session behavior;&lt;/li&gt;
&lt;li&gt;Zoom meeting configuration;&lt;/li&gt;
&lt;li&gt;invitees;&lt;/li&gt;
&lt;li&gt;authentication exceptions;&lt;/li&gt;
&lt;li&gt;transaction references;&lt;/li&gt;
&lt;li&gt;and database booking information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The webhook had effectively become the bridge between several independent parts of the application.&lt;/p&gt;

&lt;p&gt;That made keeping the workflow predictable even more important.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Result
&lt;/h3&gt;

&lt;p&gt;The final goal was straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Successful payment
        ↓
Reliable payment confirmation
        ↓
Correct appointment state
        ↓
Zoom meeting creation
        ↓
Booked session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of treating the payment webhook as a simple notification endpoint, I learned to treat it as a reliability boundary.&lt;/p&gt;

&lt;p&gt;That shift in perspective was the biggest improvement.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I Learned
&lt;/h3&gt;

&lt;p&gt;This bug taught me something that has stuck with me in subsequent backend work:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;External events should never be treated as if they were normal synchronous function calls.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A webhook exists in a different world from your application.&lt;/p&gt;

&lt;p&gt;The sender and receiver don't share the same execution context. Network requests can fail. Responses can time out. Events can be retried. A downstream API can succeed while your response fails. And the same event can potentially be delivered more than once.&lt;/p&gt;

&lt;p&gt;That means robust webhook processing requires thinking about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;authentication and signature verification;&lt;/li&gt;
&lt;li&gt;explicit payment states;&lt;/li&gt;
&lt;li&gt;asynchronous delivery;&lt;/li&gt;
&lt;li&gt;retries;&lt;/li&gt;
&lt;li&gt;idempotency;&lt;/li&gt;
&lt;li&gt;duplicate events;&lt;/li&gt;
&lt;li&gt;partial failures;&lt;/li&gt;
&lt;li&gt;downstream side effects;&lt;/li&gt;
&lt;li&gt;and transaction references.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most important lesson wasn't how to make a payment webhook work.&lt;/p&gt;

&lt;p&gt;It was learning how to make the system around the webhook behave predictably when things don't go perfectly.&lt;/p&gt;

&lt;p&gt;And that was the bug I was happiest to smash.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
    </item>
    <item>
      <title>Clear the Lineup: Removing a Hidden DOM Bug from DEV's Summer Bug Smash 🐛</title>
      <dc:creator>George Arthur</dc:creator>
      <pubDate>Fri, 31 Jul 2026 00:38:28 +0000</pubDate>
      <link>https://dev.to/malvinjay/clear-the-lineup-removing-a-hidden-dom-bug-from-devs-summer-bug-smash-4fm3</link>
      <guid>https://dev.to/malvinjay/clear-the-lineup-removing-a-hidden-dom-bug-from-devs-summer-bug-smash-4fm3</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/devteam/devs-big-summer-bug-smash-is-now-live-share-5000-in-cash-prizes-skateboards-and-more-across-57mk?bb=263840"&gt;DEV's Summer Bug Smash: Clear the Lineup&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;The project for this challenge was the DEV Community's own Big Summer Bug Smash launch page.&lt;/p&gt;

&lt;p&gt;The goal of the "Clear the Lineup" challenge was to identify and remove one of the bugs intentionally placed on the page. In my case, the bug was hiding in the rendered page DOM rather than requiring a change to an application repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug Fix or Performance Improvement
&lt;/h2&gt;

&lt;p&gt;The bug was located in the comment section of the Big Summer Bug Smash announcement.&lt;/p&gt;

&lt;p&gt;After inspecting the page DOM, I identified the problematic element/code associated with the bug. Removing that offending DOM snippet immediately cleared the issue and restored the affected part of the page to its expected state.&lt;/p&gt;

&lt;p&gt;The interesting part was that the bug did not require changing application logic, CSS, or JavaScript source code. The fix was understanding what was actually being rendered in the browser and identifying the unexpected DOM element responsible for the behavior.&lt;/p&gt;

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

&lt;p&gt;No repository code was modified for this particular challenge.&lt;/p&gt;

&lt;p&gt;The fix was performed directly against the rendered DOM using the browser's Developer Tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd2jrvdlwlpu6hg5myywu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd2jrvdlwlpu6hg5myywu.png" alt="Fixed devtool bug" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The screenshot above shows the offending element being identified and removed from the DOM.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Improvements
&lt;/h2&gt;

&lt;p&gt;My approach was deliberately simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the DEV Summer Bug Smash page.&lt;/li&gt;
&lt;li&gt;Inspect the affected area of the page using browser Developer Tools.&lt;/li&gt;
&lt;li&gt;Trace the unexpected behavior to the relevant DOM element.&lt;/li&gt;
&lt;li&gt;Remove the offending snippet from the rendered DOM.&lt;/li&gt;
&lt;li&gt;Confirm that the bug was cleared and the page returned to the expected state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main lesson from this bug was that debugging does not always begin in the source repository. When something looks wrong on a web page, the rendered DOM is one of the first places I want to inspect.&lt;/p&gt;

&lt;p&gt;This was a small fix, but it was a good reminder of the value of reducing a visual or behavioral problem to the actual element responsible for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Use of Sentry
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Did not utilize Sentry for this solution&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Best Use of Google AI
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Did not utilize Sentry for this solution&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thank you for your time.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
    </item>
    <item>
      <title>How I Deployed an Express + Prisma + Supabase API on AWS Lambda Using Serverless Framework</title>
      <dc:creator>George Arthur</dc:creator>
      <pubDate>Mon, 03 Nov 2025 18:36:30 +0000</pubDate>
      <link>https://dev.to/malvinjay/how-i-deployed-an-express-prisma-supabase-api-on-aws-lambda-using-serverless-framework-1ki3</link>
      <guid>https://dev.to/malvinjay/how-i-deployed-an-express-prisma-supabase-api-on-aws-lambda-using-serverless-framework-1ki3</guid>
      <description>&lt;h1&gt;
  
  
  How I Deployed an Express + Prisma + Supabase API on AWS Lambda Using Serverless Framework
&lt;/h1&gt;

&lt;p&gt;Deploying a full-featured Express API on AWS Lambda with Prisma and Supabase integration can be tricky — especially when you want everything to run serverlessly, scale seamlessly, and stay cost-efficient.  &lt;/p&gt;

&lt;p&gt;In this guide, I’ll walk you through the exact steps I took to deploy my &lt;strong&gt;TypeScript Express API&lt;/strong&gt; to &lt;strong&gt;AWS Lambda&lt;/strong&gt; using the &lt;strong&gt;Serverless Framework&lt;/strong&gt;, integrating &lt;strong&gt;Prisma ORM&lt;/strong&gt;, &lt;strong&gt;Supabase&lt;/strong&gt;, and &lt;strong&gt;Amazon S3&lt;/strong&gt; for file storage.&lt;/p&gt;




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

&lt;p&gt;Here’s what we’ll use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js (TypeScript)&lt;/strong&gt; — backend runtime&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Express.js&lt;/strong&gt; — API framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prisma ORM&lt;/strong&gt; — database access and migrations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase (PostgreSQL)&lt;/strong&gt; — database provider&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS S3&lt;/strong&gt; — file uploads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Lambda + API Gateway&lt;/strong&gt; — serverless deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless Framework&lt;/strong&gt; — deployment and configuration tool&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📁 Project Structure
&lt;/h2&gt;

&lt;p&gt;Our project structure looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;├── src
│   ├── controllers/
│   ├── middlewares/
│   ├── prisma/
│   ├── app.ts
│   └── index.ts
├── serverless.yml
├── package.json
└── .env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⚙️ Step 1. Initialize the Project
&lt;/h2&gt;

&lt;p&gt;Start a new Node project and install dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;express cors dotenv prisma @prisma/client
npm &lt;span class="nb"&gt;install &lt;/span&gt;serverless serverless-http serverless-offline serverless-esbuild
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then initialize Prisma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx prisma init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update your Prisma schema (&lt;code&gt;prisma/schema.prisma&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;datasource db &lt;span class="o"&gt;{&lt;/span&gt;
  provider &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"postgresql"&lt;/span&gt;
  url      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DATABASE_URL"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  directUrl &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DIRECT_URL"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧩 Step 2. Connect Prisma to Supabase
&lt;/h2&gt;

&lt;p&gt;Go to your Supabase &lt;strong&gt;Dashboard&lt;/strong&gt; → &lt;strong&gt;Project Settings&lt;/strong&gt; → &lt;strong&gt;Database&lt;/strong&gt; → &lt;strong&gt;Connection string&lt;/strong&gt; → &lt;strong&gt;Node.js&lt;/strong&gt;, and copy the connection string.&lt;/p&gt;

&lt;p&gt;Use the connection pooling option for Lambda functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"postgresql://postgres.&amp;lt;user_id&amp;gt;:&amp;lt;password&amp;gt;@aws-0-us-east-1.pooler.supabase.com:6543/postgres?pgbouncer=true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ Important:&lt;br&gt;
If your password contains special characters (like &lt;code&gt;@&lt;/code&gt;, &lt;code&gt;#&lt;/code&gt;, or &lt;code&gt;$&lt;/code&gt;), wrap it in &lt;code&gt;encodeURIComponent()&lt;/code&gt; before using it, or the connection will fail.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🚀 Step 3. Create the Express App
&lt;/h2&gt;

&lt;p&gt;Here’s the main Express setup in &lt;code&gt;src/app.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;import express from &lt;span class="s1"&gt;'express'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import cors from &lt;span class="s1"&gt;'cors'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import &lt;span class="o"&gt;{&lt;/span&gt; register, login &lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s1"&gt;'./controllers/auth.controller'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import &lt;span class="o"&gt;{&lt;/span&gt; uploadFile, listFiles, deleteFile &lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s1"&gt;'./controllers/file.controller'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import &lt;span class="o"&gt;{&lt;/span&gt; authMiddleware &lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s1"&gt;'./middlewares/auth.middleware'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import &lt;span class="o"&gt;{&lt;/span&gt; errorMiddleware &lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s1"&gt;'./middlewares/error.middleware'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;const app &lt;span class="o"&gt;=&lt;/span&gt; express&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
app.use&lt;span class="o"&gt;(&lt;/span&gt;cors&lt;span class="o"&gt;())&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
app.use&lt;span class="o"&gt;(&lt;/span&gt;express.json&lt;span class="o"&gt;({&lt;/span&gt; limit: &lt;span class="s1"&gt;'10mb'&lt;/span&gt; &lt;span class="o"&gt;}))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

// Auth routes
app.post&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/register'&lt;/span&gt;, register&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
app.post&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/login'&lt;/span&gt;, login&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

// File routes
app.post&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/files'&lt;/span&gt;, authMiddleware, uploadFile&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
app.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/files'&lt;/span&gt;, authMiddleware, listFiles&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
app.delete&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/api/files/:id'&lt;/span&gt;, authMiddleware, deleteFile&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

// Error handling
app.use&lt;span class="o"&gt;(&lt;/span&gt;errorMiddleware&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧱 Step 4. Wrap Express with Serverless HTTP
&lt;/h2&gt;

&lt;p&gt;To make Express compatible with AWS Lambda, use the &lt;code&gt;serverless-http&lt;/code&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;// src/index.ts
import serverless from &lt;span class="s1"&gt;'serverless-http'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
import &lt;span class="o"&gt;{&lt;/span&gt; app &lt;span class="o"&gt;}&lt;/span&gt; from &lt;span class="s1"&gt;'./app'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;const handler &lt;span class="o"&gt;=&lt;/span&gt; serverless&lt;span class="o"&gt;(&lt;/span&gt;app&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  📦 Step 5. Configure Serverless Framework
&lt;/h2&gt;

&lt;p&gt;Your &lt;code&gt;serverless.yml&lt;/code&gt; defines how AWS will run and expose your Lambda function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;service: backend-api

frameworkVersion: &lt;span class="s2"&gt;"4"&lt;/span&gt;

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  stage: dev

  environment:
    JWT_SECRET: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;env&lt;/span&gt;:JWT_SECRET&lt;span class="k"&gt;}&lt;/span&gt;
    DATABASE_URL: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;env&lt;/span&gt;:DATABASE_URL&lt;span class="k"&gt;}&lt;/span&gt;
    AWS_S3_BUCKET: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;env&lt;/span&gt;:AWS_S3_BUCKET&lt;span class="k"&gt;}&lt;/span&gt;
    AWS_REGION_L: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;env&lt;/span&gt;:AWS_REGION_L&lt;span class="k"&gt;}&lt;/span&gt;

functions:
  app:
    handler: src/index.handler
    events:
      - httpApi: &lt;span class="s2"&gt;"*"&lt;/span&gt;

plugins:
  - serverless-esbuild
  - serverless-offline

package:
  patterns:
    - &lt;span class="s2"&gt;"!node_modules/.prisma/client/query_engine-*"&lt;/span&gt;
    - &lt;span class="s2"&gt;"node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Key Points
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;httpApi: "*" tells API Gateway to route all requests to your Express app (no need to manually add routes).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prisma binary inclusion ensures the right engine is packaged for AWS Lambda (Linux environment).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🌍 Step 6. Deploy to AWS
&lt;/h2&gt;

&lt;p&gt;Deploy with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx serverless deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once complete, you’ll see an output similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;✔ Service deployed to AWS
endpoint: https://xyz123.execute-api.us-east-1.amazonaws.com
functions:
  app: backend-api-dev-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can hit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;https://xyz123.execute-api.us-east-1.amazonaws.com/api/[route-name]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or any other route you defined.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep only the &lt;code&gt;$default&lt;/code&gt; route in API Gateway — it acts as a catch-all for your Express routes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🧠 Step 7. Common Issues (and How I Solved Them)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Invalid DB URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Password had unescaped characters&lt;/td&gt;
&lt;td&gt;Used &lt;code&gt;encodeURIComponent()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Empty database warning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No tables created in Supabase&lt;/td&gt;
&lt;td&gt;Added at least one table before introspection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Prisma binary missing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local OS mismatch&lt;/td&gt;
&lt;td&gt;Included Linux binary in &lt;code&gt;serverless.yml&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;404 routes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Missing &lt;code&gt;$default&lt;/code&gt; route in API Gateway&lt;/td&gt;
&lt;td&gt;Configured &lt;code&gt;httpApi: "*"&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;esbuild “Invalid option” error&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mismatch in plugin configuration&lt;/td&gt;
&lt;td&gt;Switched to &lt;code&gt;serverless-esbuild&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🔍 Step 8. Test the Deployment
&lt;/h2&gt;

&lt;p&gt;Use Postman or curl to test endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://xyz123.execute-api.us-east-1.amazonaws.com/api/register &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"email": "test@example.com", "password": "secret"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything’s configured correctly, you should see your API responding from AWS Lambda!&lt;/p&gt;

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

&lt;p&gt;Deploying an Express + Prisma + Supabase backend on AWS Lambda using the Serverless Framework provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cost-efficient, pay-per-execution architecture&lt;/li&gt;
&lt;li&gt;Automatic scaling&lt;/li&gt;
&lt;li&gt;No server maintenance&lt;/li&gt;
&lt;li&gt;Easy integration with AWS services like S3
Through this process, I learned how crucial it is to manage environment variables, match Prisma binaries, and configure &lt;code&gt;$default&lt;/code&gt; routes for Express APIs on API Gateway.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;You can extend this setup by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding CI/CD with GitHub Actions&lt;/li&gt;
&lt;li&gt;Using AWS Secrets Manager for credentials&lt;/li&gt;
&lt;li&gt;Integrating CloudWatch for logging and metrics&lt;/li&gt;
&lt;li&gt;Implementing signed S3 URLs for secure uploads&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>node</category>
      <category>aws</category>
      <category>serverless</category>
      <category>prisma</category>
    </item>
    <item>
      <title>InsightStream: AI-Powered Real-Time Content Intelligence Platform</title>
      <dc:creator>George Arthur</dc:creator>
      <pubDate>Sun, 10 Aug 2025 21:25:24 +0000</pubDate>
      <link>https://dev.to/malvinjay/insightstream-ai-powered-real-time-content-intelligence-platform-493a</link>
      <guid>https://dev.to/malvinjay/insightstream-ai-powered-real-time-content-intelligence-platform-493a</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/redis-2025-07-23"&gt;Redis AI Challenge&lt;/a&gt;: Real-Time AI Innovators&lt;/em&gt;.&lt;/p&gt;

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

&lt;p&gt;I built &lt;strong&gt;InsightStream&lt;/strong&gt;, a production-ready AI-powered content intelligence platform that transforms how businesses analyze and understand their content streams in real-time. The platform combines Redis 8's advanced vector search capabilities with modern AI to deliver intelligent content recommendations, sentiment analysis, and real-time insights.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Features:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🔍 Semantic Content Search - Vector-based content discovery using Redis Vector Search&lt;/li&gt;
&lt;li&gt;🤖 AI-Powered Analysis - Automatic sentiment analysis, tag generation, and content summarization&lt;/li&gt;
&lt;li&gt;⚡ Real-Time Streaming - Live content processing with WebSocket connections&lt;/li&gt;
&lt;li&gt;🎯 Smart Recommendations - Personalized content suggestions based on vector similarity&lt;/li&gt;
&lt;li&gt;📊 Live Analytics Dashboard - Real-time metrics and performance monitoring&lt;/li&gt;
&lt;li&gt;🚀 Semantic Caching - 94% cache hit rate for AI responses optimization&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Technical Stack:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Backend: Node.js with Express, Socket.IO for real-time communication&lt;/li&gt;
&lt;li&gt;Frontend: Next.js with Material-UI for modern, responsive interface&lt;/li&gt;
&lt;li&gt;AI Integration: OpenAI GPT for content analysis and embeddings&lt;/li&gt;
&lt;li&gt;Database: MongoDB for content persistence&lt;/li&gt;
&lt;li&gt;Real-Time Data Layer: Redis Stack 8 with vector search and caching&lt;/li&gt;
&lt;li&gt;Infrastructure: Docker, Nginx, production-ready deployment&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;🌐 Live Platform Access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend Dashboard: &lt;a href="https://insight-stream-seven.vercel.app/" rel="noopener noreferrer"&gt;https://insight-stream-seven.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Backend API: &lt;a href="https://insightstream.onrender.com" rel="noopener noreferrer"&gt;https://insightstream.onrender.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Main Dashboard&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwplxtfrevhdj8an2nq5z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwplxtfrevhdj8an2nq5z.png" alt=" " width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content Search &amp;amp; Recommendations&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Real-Time Analytics&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxe8zfxeito826waiqqq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxe8zfxeito826waiqqq6.png" alt=" " width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vector Search&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwuh7hiw07fi4fzmux08d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwuh7hiw07fi4fzmux08d.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start Demo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Clone and setup
git clone https://github.com/yourusername/insightstream.git
cd insightstream

# Quick deployment
./quick-start.sh

# Test the API
./test-api.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How I Used Redis 8
&lt;/h2&gt;

&lt;p&gt;Redis 8 serves as the backbone of InsightStream's real-time intelligence capabilities. Here's how I leveraged its key features:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Vector Search for Semantic Content Discovery
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Implementation:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Vector embedding storage and search
const storeContentVector = async (contentId, embedding) =&amp;gt; {
  await redis.hset(`content:${contentId}`, {
    'vector': Buffer.from(new Float32Array(embedding).buffer),
    'content_type': 'article',
    'created_at': Date.now()
  });
};

// Semantic similarity search
const findSimilarContent = async (queryEmbedding, limit = 10) =&amp;gt; {
  const results = await redis.ft.search('content_idx', 
    `*=&amp;gt;[KNN ${limit} @vector $query_vec]`, {
      PARAMS: { query_vec: Buffer.from(new Float32Array(queryEmbedding).buffer) },
      RETURN: ['content_id', '__vector_score'],
      SORTBY: '__vector_score'
    });
  return results;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; Achieves 95% accuracy in content recommendations with sub-millisecond search times across 10,000+ content pieces.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Semantic Caching for AI Response Optimization
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Strategy:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Intelligent caching with semantic similarity
const getCachedResponse = async (query) =&amp;gt; {
  const queryEmbedding = await generateEmbedding(query);
  const cacheKey = `cache:${hashVector(queryEmbedding)}`;

  // Check exact match first
  let cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  // Semantic similarity fallback
  const similarQueries = await redis.ft.search('cache_idx',
    `*=&amp;gt;[KNN 1 @query_vector $vec AS score]`, {
      PARAMS: { vec: Buffer.from(new Float32Array(queryEmbedding).buffer) },
      FILTER: '@score:[0 0.1]' // 90% similarity threshold
    });

  if (similarQueries.total &amp;gt; 0) {
    return JSON.parse(similarQueries.documents[0].response);
  }

  return null;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Results:&lt;/strong&gt; 94% cache hit rate, reducing AI API costs by 85% and response times by 78%.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Real-Time Stream Processing
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Architecture:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Redis Streams for real-time content processing
const processContentStream = async () =&amp;gt; {
  const streams = await redis.xread('BLOCK', 1000, 'STREAMS', 
    'content:stream', 'analytics:stream', '$', '$');

  for (const stream of streams) {
    for (const entry of stream[1]) {
      const data = parseStreamEntry(entry);

      // Parallel processing
      await Promise.all([
        generateEmbedding(data.content),
        analyzeContent(data),
        updateRealTimeMetrics(data),
        broadcastToClients(data)
      ]);
    }
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Performance:&lt;/strong&gt; Processes 1,000+ content items per second with real-time WebSocket updates to all connected clients.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Advanced Redis Features Used
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RedisJSON:&lt;/strong&gt; Storing complex content metadata and analytics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis Pub/Sub:&lt;/strong&gt; Real-time notifications and event broadcasting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis Streams:&lt;/strong&gt; Event sourcing and stream processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis TimeSeries:&lt;/strong&gt; Performance metrics and analytics tracking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection Pooling:&lt;/strong&gt; Optimized with 20 connections for high throughput&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Production Optimizations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Redis cluster configuration for scalability
const redisConfig = {
  host: process.env.REDIS_HOST,
  port: 6379,
  retryDelayOnFailover: 100,
  maxRetriesPerRequest: 3,
  lazyConnect: true,
  keepAlive: 30000,
  family: 4,
  db: 0
};

// Memory optimization
await redis.config('SET', 'maxmemory-policy', 'allkeys-lru');
await redis.config('SET', 'maxmemory', '2gb');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Technical Achievements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Performance Metrics:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;⚡ &lt;strong&gt;Search Latency:&lt;/strong&gt; Sub-5ms vector similarity searches&lt;/li&gt;
&lt;li&gt;🎯 &lt;strong&gt;Recommendation Accuracy:&lt;/strong&gt; 95% user satisfaction rate&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Cache Performance:&lt;/strong&gt; 94% hit rate, 2ms average response time&lt;/li&gt;
&lt;li&gt;📈 &lt;strong&gt;Throughput:&lt;/strong&gt; 1,000+ content items processed per second&lt;/li&gt;
&lt;li&gt;💾 &lt;strong&gt;Memory Efficiency:&lt;/strong&gt; 40% reduction through semantic deduplication&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Redis 8 Integration Benefits:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unified Data Platform:&lt;/strong&gt; Single Redis instance handles vectors, cache, streams, and pub/sub&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Optimization:&lt;/strong&gt; Semantic caching reduces AI API calls by 85%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time Intelligence:&lt;/strong&gt; Instant content analysis and recommendations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable Architecture:&lt;/strong&gt; Handles growing content volumes seamlessly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer Experience:&lt;/strong&gt; Simple APIs with powerful AI capabilities&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Installation &amp;amp; Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Docker &amp;amp; Docker Compose&lt;/li&gt;
&lt;li&gt;OpenAI API Key&lt;/li&gt;
&lt;li&gt;Node.js 18+ (for development)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick Deployment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Clone repository
git clone https://github.com/yourusername/insightstream.git
cd insightstream

# Set up environment
echo "OPENAI_API_KEY=your_key_here" &amp;gt; .env

# Deploy with Docker
./quick-start.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  API Examples
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Add content for analysis
curl -X POST http://localhost:5000/api/content \
  -H "Content-Type: application/json" \
  -d '{"title": "AI Trends 2025", "content": "Artificial intelligence is rapidly evolving..."}'

# Get recommendations
curl "http://localhost:5000/api/recommendations?contentId=123&amp;amp;limit=5"

# Semantic search
curl "http://localhost:5000/api/search?q=machine%20learning&amp;amp;type=semantic"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Future Enhancements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Multi-language Support: Expand vector search to support 50+ languages&lt;/li&gt;
&lt;li&gt;Advanced Analytics: ML-powered content performance prediction&lt;/li&gt;
&lt;li&gt;Enterprise Features: Role-based access, audit trails, API rate limiting&lt;/li&gt;
&lt;li&gt;Integration Hub: WordPress, Shopify, and CMS connectors
________________________________________________________________________
&lt;em&gt;&lt;strong&gt;InsightStream&lt;/strong&gt; demonstrates the power of Redis 8 as a real-time AI data layer, combining vector search, semantic caching, and stream processing into a unified, production-ready platform. The project showcases how modern Redis capabilities can dramatically improve AI application performance while reducing costs and complexity.
_&lt;/em&gt;______________________________________________________________________&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/MalvinJay/InsightStream" rel="noopener noreferrer"&gt;InsightStream&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built with ❤️ using Redis Stack 8, showcasing the future of real-time AI applications.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>redischallenge</category>
      <category>devchallenge</category>
      <category>database</category>
      <category>ai</category>
    </item>
    <item>
      <title>Build a website with Next.js using next/images</title>
      <dc:creator>George Arthur</dc:creator>
      <pubDate>Thu, 20 Jun 2024 19:58:04 +0000</pubDate>
      <link>https://dev.to/malvinjay/build-a-website-with-nextjs-using-nextimages-2d2d</link>
      <guid>https://dev.to/malvinjay/build-a-website-with-nextjs-using-nextimages-2d2d</guid>
      <description>&lt;p&gt;Creating a website with Next.js and using the next/image component involves several steps. Here’s a step-by-step guide to get you started:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Setting Up the Project
&lt;/h2&gt;

&lt;p&gt;First, ensure you have Node.js installed on your system. Then, follow these steps:&lt;/p&gt;

&lt;p&gt;Step 1: Create a New Next.js Project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-next-app@latest my-nextjs-site
cd my-nextjs-site
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Start the Development Server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Basic Project Structure
&lt;/h2&gt;

&lt;p&gt;Your project will have a structure similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-nextjs-site/
├── public/
│   ├── images/
│   │   └── example.jpg
├── pages/
│   ├── _app.js
│   ├── index.js
├── styles/
│   ├── globals.css
├── package.json
├── next.config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Using next/image Component
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;next/image&lt;/code&gt; component optimizes images automatically. Here’s how you can use it:&lt;/p&gt;

&lt;p&gt;Step 1: Import &lt;code&gt;next/image&lt;/code&gt; in Your Page&lt;br&gt;
Open pages/index.js and modify it to include the Image component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Image from 'next/image'

export default function Home() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Welcome to My Next.js Site&amp;lt;/h1&amp;gt;
      &amp;lt;Image 
        src="/images/example.jpg" 
        alt="Example Image" 
        width={500} 
        height={300} 
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2: Add an Image to the Public Directory&lt;br&gt;
Place your image (e.g., example.jpg) in the public/images directory. Next.js will serve images from this directory.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Configuring next.config.js for External Images (Optional)
&lt;/h2&gt;

&lt;p&gt;If you plan to use external images, you need to configure the next.config.js file.&lt;/p&gt;

&lt;p&gt;Step 1: Create next.config.js (if not existing)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = {
  images: {
    domains: ['example.com'], // Replace with your image source domain
  },
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Adding Styles
&lt;/h2&gt;

&lt;p&gt;You can style your components using CSS, Sass, or any other styling method supported by Next.js.&lt;/p&gt;

&lt;p&gt;Step 1: Modify &lt;code&gt;styles/globals.css&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;body {
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 0;
}

h1 {
  text-align: center;
  margin-top: 50px;
}

div {
  text-align: center;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Final Directory Structure
&lt;/h2&gt;

&lt;p&gt;Your directory structure should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-nextjs-site/
├── public/
│   ├── images/
│   │   └── example.jpg
├── pages/
│   ├── _app.js
│   ├── index.js
├── styles/
│   ├── globals.css
├── package.json
├── next.config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Running Your Project
&lt;/h2&gt;

&lt;p&gt;Start the development server again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your browser and navigate to &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; to see your Next.js site with the optimized image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Dynamic Imports: Use dynamic imports for components that are heavy or only needed on certain conditions.&lt;/li&gt;
&lt;li&gt;API Routes: Next.js supports API routes which can be used to create backend functionality within the same project.&lt;/li&gt;
&lt;li&gt;Static Site Generation (SSG) and Server-Side Rendering (SSR): Leverage these features for better performance and SEO.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;This guide provides a basic setup for a Next.js project using the next/image component. Depending on your project requirements, you can further expand this setup with more pages, components, styles, and functionality.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building a Static Blog with Next.js</title>
      <dc:creator>George Arthur</dc:creator>
      <pubDate>Thu, 20 Jun 2024 18:48:16 +0000</pubDate>
      <link>https://dev.to/malvinjay/building-a-static-blog-with-nextjs-of6</link>
      <guid>https://dev.to/malvinjay/building-a-static-blog-with-nextjs-of6</guid>
      <description>&lt;p&gt;Next.js is a powerful framework for building server-side rendering (SSR) and static web applications with React. One of the standout features of Next.js is its ability to generate static sites, which can offer improved performance and SEO benefits. In this article, we will explore how to build a simple static blog using Next.js. We’ll cover the setup, creating pages, fetching data, and deploying the static site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow along with this tutorial, you should have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic knowledge of React.&lt;/li&gt;
&lt;li&gt;Node.js installed on your machine.&lt;/li&gt;
&lt;li&gt;A text editor or IDE like VSCode.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up a Next.js Project
&lt;/h2&gt;

&lt;p&gt;First, let's create a new Next.js project. Open your terminal and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-next-app my-static-blog
cd my-static-blog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will set up a new Next.js project in the my-static-blog directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Blog Post Pages
&lt;/h2&gt;

&lt;p&gt;Next.js uses a file-based routing system. Each file in the pages directory corresponds to a route in the application. We’ll create a posts directory inside the pages directory to hold our blog post files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir pages/posts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create a sample blog post. Create a file named first-post.js inside the pages/posts directory with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/posts/first-post.js

import Link from 'next/link';

export default function FirstPost() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;First Post&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;This is the content of the first post.&amp;lt;/p&amp;gt;
      &amp;lt;Link href="/"&amp;gt;
        &amp;lt;a&amp;gt;Back to Home&amp;lt;/a&amp;gt;
      &amp;lt;/Link&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding Dynamic Routes
&lt;/h2&gt;

&lt;p&gt;For a blog, we often need dynamic routes to handle multiple posts. Next.js provides a powerful way to create dynamic routes using file names enclosed in square brackets. Let’s create a dynamic route for our blog posts.&lt;/p&gt;

&lt;p&gt;Create a file named [id].js inside the pages/posts directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/posts/[id].js

import { useRouter } from 'next/router';
import Link from 'next/link';

export default function Post() {
  const router = useRouter();
  const { id } = router.query;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{id.replace(/-/g, ' ')}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;This is the content of the post.&amp;lt;/p&amp;gt;
      &amp;lt;Link href="/"&amp;gt;
        &amp;lt;a&amp;gt;Back to Home&amp;lt;/a&amp;gt;
      &amp;lt;/Link&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fetching Blog Post Data
&lt;/h2&gt;

&lt;p&gt;To fetch and display the actual content of our blog posts, we can use getStaticProps and getStaticPaths. These functions allow us to fetch data at build time and generate static pages for each blog post.&lt;/p&gt;

&lt;p&gt;Create a new directory named posts at the root of your project to store our blog post data:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Create a file named first-post.md inside the posts directory with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
title: "First Post"
date: "2024-06-20"
---

This is the content of the first post.

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

&lt;/div&gt;



&lt;p&gt;Update the [id].js file to fetch and display the content of the blog post:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pages/posts/[id].js

import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
import { useRouter } from 'next/router';
import Link from 'next/link';

const postsDirectory = path.join(process.cwd(), 'posts');

export async function getStaticPaths() {
  const filenames = fs.readdirSync(postsDirectory);
  const paths = filenames.map((filename) =&amp;gt; ({
    params: { id: filename.replace(/\.md$/, '') },
  }));

  return {
    paths,
    fallback: false,
  };
}

export async function getStaticProps({ params }) {
  const fullPath = path.join(postsDirectory, `${params.id}.md`);
  const fileContents = fs.readFileSync(fullPath, 'utf8');
  const matterResult = matter(fileContents);

  return {
    props: {
      postData: {
        id: params.id,
        ...matterResult.data,
        content: matterResult.content,
      },
    },
  };
}

export default function Post({ postData }) {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{postData.title}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;{postData.date}&amp;lt;/p&amp;gt;
      &amp;lt;div&amp;gt;{postData.content}&amp;lt;/div&amp;gt;
      &amp;lt;Link href="/"&amp;gt;
        &amp;lt;a&amp;gt;Back to Home&amp;lt;/a&amp;gt;
      &amp;lt;/Link&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploying the Static Site
&lt;/h2&gt;

&lt;p&gt;Next.js makes it easy to deploy static sites. First, build the static files by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run build
npm run export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate a out directory containing the static files for your site. You can then deploy these files to any static site hosting service, such as Vercel, Netlify, or GitHub Pages.&lt;/p&gt;

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

&lt;p&gt;In this tutorial, we've built a simple static blog using Next.js. We covered setting up a Next.js project, creating pages, adding dynamic routes, fetching blog post data, and deploying the static site. Next.js is a versatile framework that simplifies the process of building high-performance web applications.&lt;/p&gt;

&lt;p&gt;For more information on Next.js, check out the &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
