<?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: Billy Okeyo</title>
    <description>The latest articles on DEV Community by Billy Okeyo (@billy_de_cartel).</description>
    <link>https://dev.to/billy_de_cartel</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%2F363015%2Fb7cad6bd-0cc8-4b82-b298-5fcdfcfe48cf.jpg</url>
      <title>DEV Community: Billy Okeyo</title>
      <link>https://dev.to/billy_de_cartel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/billy_de_cartel"/>
    <language>en</language>
    <item>
      <title>Idempotency Explained: Building APIs That Survive Retries</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/idempotency-explained-building-apis-that-survive-retries-1hlk</link>
      <guid>https://dev.to/billy_de_cartel/idempotency-explained-building-apis-that-survive-retries-1hlk</guid>
      <description>&lt;p&gt;Imagine you're purchasing a product online.&lt;/p&gt;

&lt;p&gt;You click the &lt;strong&gt;"Pay Now"&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;Nothing happens.&lt;/p&gt;

&lt;p&gt;After a few seconds, you assume the request failed, so you click the button again.&lt;/p&gt;

&lt;p&gt;And again.&lt;/p&gt;

&lt;p&gt;A few minutes later, you discover you've been charged three times.&lt;/p&gt;

&lt;p&gt;What happened?&lt;/p&gt;

&lt;p&gt;From the user's perspective, the payment seemed to fail. From the server's perspective, however, it successfully processed every request it received.&lt;/p&gt;

&lt;p&gt;This is one of the most common problems in distributed systems, and it's exactly why idempotency exists.&lt;/p&gt;

&lt;p&gt;Whether you're building payment systems, booking platforms, inventory management software, or any API that changes data, retries are inevitable. Networks fail, clients time out, mobile connections drop, and users double-click buttons.&lt;/p&gt;

&lt;p&gt;A well-designed API should survive these retries without creating duplicate side effects.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore what idempotency is, why it matters, and how to implement it in your own APIs.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Is Idempotency?
&lt;/h1&gt;

&lt;p&gt;In simple terms, &lt;strong&gt;an idempotent operation can be performed multiple times without changing the final result beyond the first successful execution.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Turn on the light.
Turn on the light again.
Turn on the light again.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The light is still &lt;strong&gt;ON&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nothing new happened after the first request.&lt;/p&gt;

&lt;p&gt;The final state remains the same.&lt;/p&gt;

&lt;p&gt;That's idempotency.&lt;/p&gt;

&lt;p&gt;Now compare it with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deposit $100
Deposit $100
Deposit $100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your account balance increases by &lt;strong&gt;$300&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This operation is &lt;strong&gt;not idempotent&lt;/strong&gt; because every request changes the system.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Retries Happen
&lt;/h1&gt;

&lt;p&gt;Many developers assume users only send one request.&lt;/p&gt;

&lt;p&gt;Reality is different.&lt;/p&gt;

&lt;p&gt;Requests are retried because of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow internet connections&lt;/li&gt;
&lt;li&gt;Gateway timeouts&lt;/li&gt;
&lt;li&gt;Reverse proxies&lt;/li&gt;
&lt;li&gt;Mobile network interruptions&lt;/li&gt;
&lt;li&gt;Browser refreshes&lt;/li&gt;
&lt;li&gt;Double-clicking buttons&lt;/li&gt;
&lt;li&gt;Client retry mechanisms&lt;/li&gt;
&lt;li&gt;Load balancers&lt;/li&gt;
&lt;li&gt;Microservice communication failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Imagine this timeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client -------- POST /payments --------&amp;gt; API

             Payment succeeds

API -------- 200 OK --------X

(Response never reaches client)

Client waits...

Client retries.

POST /payments again.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client believes the payment failed.&lt;/p&gt;

&lt;p&gt;The server already completed it.&lt;/p&gt;

&lt;p&gt;Without idempotency...&lt;/p&gt;

&lt;p&gt;The payment happens twice.&lt;/p&gt;




&lt;h1&gt;
  
  
  HTTP Methods and Idempotency
&lt;/h1&gt;

&lt;p&gt;HTTP itself distinguishes between idempotent and non-idempotent methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  GET
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /users/10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the user.&lt;/p&gt;

&lt;p&gt;Call it once.&lt;/p&gt;

&lt;p&gt;Call it 100 times.&lt;/p&gt;

&lt;p&gt;Nothing changes.&lt;/p&gt;

&lt;p&gt;Idempotent&lt;/p&gt;




&lt;h3&gt;
  
  
  PUT
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;PUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/users/&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Billy"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replacing the same resource repeatedly produces the same result.&lt;/p&gt;

&lt;p&gt;Idempotent&lt;/p&gt;




&lt;h3&gt;
  
  
  DELETE
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;DELETE /users/10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete the user.&lt;/p&gt;

&lt;p&gt;Deleting an already deleted user doesn't delete them twice.&lt;/p&gt;

&lt;p&gt;The final state is still:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User does not exist.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Idempotent&lt;/p&gt;




&lt;h3&gt;
  
  
  POST
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /orders
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new order.&lt;/p&gt;

&lt;p&gt;Call it twice.&lt;/p&gt;

&lt;p&gt;You now have two orders.&lt;/p&gt;

&lt;p&gt;Not idempotent&lt;/p&gt;

&lt;p&gt;This is why POST requests often require additional protection.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Payment APIs Use Idempotency Keys
&lt;/h1&gt;

&lt;p&gt;Payment providers like Stripe popularized the use of &lt;strong&gt;Idempotency Keys&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The idea is simple.&lt;/p&gt;

&lt;p&gt;The client generates a unique identifier.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Idempotency-Key:
6ab89d3b-acde-4d71-b20d-483d8d0ef091
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every retry sends the same key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /payments

Idempotency-Key:
6ab89d3b-acde-4d71-b20d-483d8d0ef091
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the server receives the request:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check if this key already exists.&lt;/li&gt;
&lt;li&gt;If not, process the payment.&lt;/li&gt;
&lt;li&gt;Save both the key and the response.&lt;/li&gt;
&lt;li&gt;Return the response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the same request arrives again with the same key:&lt;/p&gt;

&lt;p&gt;Instead of charging the customer again...&lt;/p&gt;

&lt;p&gt;Return the previously stored response.&lt;br&gt;
&lt;/p&gt;

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

POST /payments
Key: ABC123

↓

Server

Charge customer

↓

Store

ABC123 → Payment #456

↓

Return success

---

Retry

POST /payments
Key: ABC123

↓

Lookup

ABC123 exists

↓

Return Payment #456

No second charge.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Implementing Idempotency
&lt;/h1&gt;

&lt;p&gt;A common workflow looks like this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1
&lt;/h2&gt;

&lt;p&gt;Receive request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /orders
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Headers&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Idempotency-Key:
XYZ987
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2
&lt;/h2&gt;

&lt;p&gt;Search database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;idempotency_keys&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'XYZ987'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Found?&lt;/p&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;Return stored response.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3
&lt;/h2&gt;

&lt;p&gt;Not found?&lt;/p&gt;

&lt;p&gt;Create the resource.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4
&lt;/h2&gt;

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

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

Response

Status Code

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

&lt;/div&gt;



&lt;p&gt;Now every retry returns the same response.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example in Node.js (Express)
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/payments&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existing&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;Idempotency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;key&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;existing&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&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;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;Idempotency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;key&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;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payment&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&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="nx"&gt;payment&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;h2&gt;
  
  
  Python (FastAPI)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.responses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;JSONResponse&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/payments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Idempotency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;request&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="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Idempotency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payment&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="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  C# (ASP.NET Core)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/payments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IdempotencyStore&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PaymentService&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Idempotency-Key"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&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="n"&gt;existing&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&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;return&lt;/span&gt; &lt;span class="n"&gt;Results&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="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaymentRequest&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;!);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IdempotencyRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&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="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status201Created&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;h2&gt;
  
  
  Go
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;createPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Idempotency-Key"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;idempotency&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FindOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="n"&gt;PaymentRequest&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;idempotency&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;IdempotencyRecord&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCreated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCreated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&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;h2&gt;
  
  
  Laravel
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/payments'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Idempotency-Key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Idempotency&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;first&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="nv"&gt;$existing&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="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$existing&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$existing&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;processPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="nc"&gt;Idempotency&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'key'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'response'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$payment&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="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;201&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 logic is surprisingly simple.&lt;/p&gt;

&lt;p&gt;The complexity comes from storing and managing the keys correctly.&lt;/p&gt;




&lt;h1&gt;
  
  
  Where Should Idempotency Keys Be Stored?
&lt;/h1&gt;

&lt;p&gt;Options include:&lt;/p&gt;

&lt;h2&gt;
  
  
  Database
&lt;/h2&gt;

&lt;p&gt;Best for most applications.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persistent&lt;/li&gt;
&lt;li&gt;Reliable&lt;/li&gt;
&lt;li&gt;Easy to query&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slightly slower&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Redis
&lt;/h2&gt;

&lt;p&gt;Excellent for high-volume APIs.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extremely fast&lt;/li&gt;
&lt;li&gt;TTL support&lt;/li&gt;
&lt;li&gt;Easy expiration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many APIs automatically expire keys after 24 hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  In-Memory
&lt;/h2&gt;

&lt;p&gt;Useful only during development.&lt;/p&gt;

&lt;p&gt;Not recommended for production.&lt;/p&gt;

&lt;p&gt;Restarting the server loses everything.&lt;/p&gt;




&lt;h1&gt;
  
  
  Common Mistakes
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Reusing Keys
&lt;/h2&gt;

&lt;p&gt;Every logical operation should have its own unique key.&lt;/p&gt;

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

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

used today

used tomorrow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

↓

Generate new UUID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Ignoring Request Differences
&lt;/h2&gt;

&lt;p&gt;Suppose the first request is:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The retry is:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Same key.&lt;/p&gt;

&lt;p&gt;Different body.&lt;/p&gt;

&lt;p&gt;The server should reject this request because the key is being reused for a different operation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Never Expiring Keys
&lt;/h2&gt;

&lt;p&gt;Keeping millions of old keys forever wastes storage.&lt;/p&gt;

&lt;p&gt;Most APIs expire them after:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;24 hours&lt;/li&gt;
&lt;li&gt;48 hours&lt;/li&gt;
&lt;li&gt;7 days&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;depending on business requirements.&lt;/p&gt;




&lt;h1&gt;
  
  
  Real-World Use Cases
&lt;/h1&gt;

&lt;p&gt;Idempotency is valuable anywhere duplicate requests could have costly consequences.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payment processing&lt;/li&gt;
&lt;li&gt;Bank transfers&lt;/li&gt;
&lt;li&gt;Order creation&lt;/li&gt;
&lt;li&gt;Hotel reservations&lt;/li&gt;
&lt;li&gt;Flight bookings&lt;/li&gt;
&lt;li&gt;Ticket purchases&lt;/li&gt;
&lt;li&gt;Subscription billing&lt;/li&gt;
&lt;li&gt;Inventory updates&lt;/li&gt;
&lt;li&gt;Webhook processing&lt;/li&gt;
&lt;li&gt;Email sending&lt;/li&gt;
&lt;li&gt;Message queues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If performing the same action twice could create an incorrect outcome, idempotency is worth considering.&lt;/p&gt;




&lt;h1&gt;
  
  
  When You Don't Need Idempotency
&lt;/h1&gt;

&lt;p&gt;Not every endpoint needs an idempotency key.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /posts
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No state changes.&lt;/p&gt;

&lt;p&gt;No duplicates.&lt;/p&gt;

&lt;p&gt;No problem.&lt;/p&gt;

&lt;p&gt;Likewise, endpoints such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search&lt;/li&gt;
&lt;li&gt;Filtering&lt;/li&gt;
&lt;li&gt;Reading reports&lt;/li&gt;
&lt;li&gt;Viewing profiles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;are already naturally idempotent.&lt;/p&gt;

&lt;p&gt;Reserve idempotency mechanisms for operations where retries could create unintended side effects.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Idempotency isn't just an implementation detail—it's a reliability feature.&lt;/p&gt;

&lt;p&gt;In distributed systems, retries are normal. Networks are unreliable, users click buttons more than once, and clients retry requests automatically. Instead of hoping those situations never happen, design your APIs to handle them gracefully.&lt;/p&gt;

&lt;p&gt;By using idempotency keys, storing responses, validating retries, and choosing the right storage strategy, you can prevent duplicate orders, repeated payments, and other costly errors.&lt;/p&gt;

&lt;p&gt;A resilient API isn't one that never receives duplicate requests.&lt;/p&gt;

&lt;p&gt;It's one that produces the correct outcome even when duplicate requests inevitably arrive.&lt;/p&gt;

&lt;p&gt;The next time you design a &lt;code&gt;POST&lt;/code&gt; endpoint, ask yourself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What happens if this request is sent twice?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is "something bad," it's probably time to add idempotency.&lt;/p&gt;

</description>
      <category>apidesign</category>
      <category>softwaretesting</category>
      <category>idempotency</category>
    </item>
    <item>
      <title>Debugging Is a Skill Nobody Teaches You</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/debugging-is-a-skill-nobody-teaches-you-1k2f</link>
      <guid>https://dev.to/billy_de_cartel/debugging-is-a-skill-nobody-teaches-you-1k2f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;You’ve been staring at the same bug for 2 hours.&lt;/em&gt;&lt;br&gt;
You’ve restarted the server. Cleared cache. Added random &lt;code&gt;console.log&lt;/code&gt;s.&lt;br&gt;
Somehow… it still doesn’t work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At some point, you stop coding and start guessing.&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%2Fbti548ebmr81put20soo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbti548ebmr81put20soo.gif" alt="Frustrated Coding" width="480" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s the real problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The issue isn’t the bug.&lt;br&gt;
It’s that nobody actually teaches debugging as a skill.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Way Most Developers Debug
&lt;/h2&gt;

&lt;p&gt;Let’s be honest. Most of us learned debugging like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sprinkle &lt;code&gt;console.log&lt;/code&gt; everywhere&lt;/li&gt;
&lt;li&gt;Change random lines and hope something works&lt;/li&gt;
&lt;li&gt;Copy-paste error messages into Google&lt;/li&gt;
&lt;li&gt;Restart everything “just in case”&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;It &lt;em&gt;sometimes&lt;/em&gt; works.&lt;/p&gt;

&lt;p&gt;But it’s slow, frustrating, and unreliable.&lt;/p&gt;

&lt;p&gt;It’s not debugging.&lt;/p&gt;

&lt;p&gt;It’s &lt;strong&gt;trial and error disguised as progress&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Debugging Actually Is
&lt;/h2&gt;

&lt;p&gt;Here’s the mindset shift that changes everything:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Debugging is not about fixing code.&lt;br&gt;
It’s about finding where your mental model diverges from reality.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You &lt;em&gt;think&lt;/em&gt; the system works one way.&lt;/p&gt;

&lt;p&gt;Reality says otherwise.&lt;/p&gt;

&lt;p&gt;Your job is to &lt;strong&gt;close that gap&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Debugging Mindset
&lt;/h2&gt;

&lt;p&gt;Before tools, before techniques, this is what matters most.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Assume Your Assumptions Are Wrong
&lt;/h3&gt;

&lt;p&gt;If something doesn’t work, at least one thing you believe is false.&lt;/p&gt;

&lt;p&gt;Your job is to find it.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Narrow the Problem Space
&lt;/h3&gt;

&lt;p&gt;Bad debugging:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Something is wrong with the app”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Good debugging:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The issue happens only when this function runs after login”&lt;/p&gt;
&lt;/blockquote&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%2Fax9kok46nh3vfbkqntku.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fax9kok46nh3vfbkqntku.gif" alt="Analyzing Clues" width="480" height="304"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Reproduce Before Fixing
&lt;/h3&gt;

&lt;p&gt;If you can’t reliably reproduce the bug, you don’t understand it.&lt;/p&gt;

&lt;p&gt;And if you don’t understand it, your fix is luck—not skill.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. One Change at a Time
&lt;/h3&gt;

&lt;p&gt;If you change 5 things and it works…&lt;br&gt;
which one fixed it?&lt;/p&gt;

&lt;p&gt;You don’t know.&lt;/p&gt;

&lt;p&gt;That’s how bugs come back later.&lt;/p&gt;


&lt;h3&gt;
  
  
  5. Understand Before You Patch
&lt;/h3&gt;

&lt;p&gt;Quick fixes feel good.&lt;/p&gt;

&lt;p&gt;Understanding the root cause makes you dangerous (in a good way).&lt;/p&gt;


&lt;h2&gt;
  
  
  A Repeatable Debugging Process
&lt;/h2&gt;

&lt;p&gt;This is where things become practical.&lt;/p&gt;


&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1: Reproduce the Bug&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Make it happen consistently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Click button → error appears  
Refresh → still happens  
Different browser → still happens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it’s inconsistent, your first task is to &lt;strong&gt;find the pattern&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Step 2: Define Expected vs Actual&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Write it down clearly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expected: API returns user data  
Actual: API returns empty array
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step alone eliminates confusion.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Step 3: Isolate the Problem&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Shrink the scope.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comment out unrelated code&lt;/li&gt;
&lt;li&gt;Remove layers (UI → API → DB)&lt;/li&gt;
&lt;li&gt;Test pieces independently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it 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;[ UI ] → [ API ] → [ Database ]

Which layer is lying?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;strong&gt;Step 4: Form a Hypothesis&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Be explicit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I think the API is returning empty data because the query filter is wrong.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now you’re not guessing—you’re &lt;strong&gt;testing a theory&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Step 5: Test the Hypothesis&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Use targeted tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logs&lt;/li&gt;
&lt;li&gt;Breakpoints&lt;/li&gt;
&lt;li&gt;Network inspector&lt;/li&gt;
&lt;/ul&gt;

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

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;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;User ID:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But intentional—not random.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Step 6: Fix and Verify&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Fix it.&lt;/p&gt;

&lt;p&gt;Then confirm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does it work in all cases?&lt;/li&gt;
&lt;li&gt;Did you break something else?&lt;/li&gt;
&lt;/ul&gt;

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




&lt;h3&gt;
  
  
  &lt;strong&gt;Step 7: Understand the Root Cause&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is where most devs stop too early.&lt;/p&gt;

&lt;p&gt;Don’t just fix it—&lt;strong&gt;explain it&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The bug happened because the state updated asynchronously, and we read it too early.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now you’ve learned something reusable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Example: “The API Is Broken” (But It’s Not)
&lt;/h2&gt;

&lt;p&gt;Let’s walk through a real scenario.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Bug
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Frontend shows: &lt;strong&gt;No data available&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Initial Assumption
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“The API is broken.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Step 1: Check Network Tab
&lt;/h3&gt;

&lt;p&gt;You open DevTools → Network:&lt;/p&gt;

&lt;p&gt;API returns correct data&lt;/p&gt;

&lt;p&gt;So… not the API.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2: Check State
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It logs:&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="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Empty array.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3: Trace the Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&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="nf"&gt;fetchData&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;Inside &lt;code&gt;fetchData&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="nf"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="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="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// still empty&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;p&gt;React state updates are &lt;strong&gt;asynchronous&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You’re logging &lt;strong&gt;before state updates&lt;/strong&gt;.&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&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="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&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%2Fr8w01awu42tbaz2qql4w.gif" alt="Victory" width="260" height="195"&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Lesson
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;The bug wasn’t in the API.&lt;br&gt;
It was in your mental model of how state updates work.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Tools That Actually Help
&lt;/h2&gt;

&lt;p&gt;Not everything is about tools—but the right ones matter.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Browser DevTools (Underrated Powerhouse)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network tab&lt;/strong&gt; → verify API calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Console&lt;/strong&gt; → inspect runtime values&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application tab&lt;/strong&gt; → check storage&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Breakpoints (Game Changer)
&lt;/h3&gt;

&lt;p&gt;Instead of spamming logs:&lt;/p&gt;

&lt;p&gt;Pause execution and inspect state &lt;em&gt;live&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Intentional Logging
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;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;here&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;Good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;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;User after login:&lt;/span&gt;&lt;span class="dl"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4. Stack Traces
&lt;/h3&gt;

&lt;p&gt;Read them.&lt;/p&gt;

&lt;p&gt;They literally tell you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where the error happened&lt;/li&gt;
&lt;li&gt;What triggered it&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5. Rubber Duck Debugging
&lt;/h3&gt;

&lt;p&gt;Explain the bug out loud.&lt;/p&gt;

&lt;p&gt;Yes, seriously.&lt;/p&gt;

&lt;p&gt;You’ll often solve it mid-explanation.&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problem → Hypothesis → Test → Learn → Repeat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Common Debugging Traps
&lt;/h2&gt;

&lt;p&gt;Avoid these and you’ll already be ahead of most devs:&lt;/p&gt;




&lt;h3&gt;
  
  
  Fixing Symptoms Instead of Causes
&lt;/h3&gt;

&lt;p&gt;You silence the error… but the bug is still there.&lt;/p&gt;




&lt;h3&gt;
  
  
  Changing Too Many Things at Once
&lt;/h3&gt;

&lt;p&gt;Now you don’t know what worked.&lt;/p&gt;




&lt;h3&gt;
  
  
  Ignoring Error Messages
&lt;/h3&gt;

&lt;p&gt;The error is literally telling you what’s wrong.&lt;/p&gt;

&lt;p&gt;Read it.&lt;/p&gt;




&lt;h3&gt;
  
  
  Assuming the Bug Is “Weird”
&lt;/h3&gt;

&lt;p&gt;It’s almost never weird.&lt;/p&gt;

&lt;p&gt;It’s misunderstood.&lt;/p&gt;




&lt;h3&gt;
  
  
  “It Works on My Machine”
&lt;/h3&gt;

&lt;p&gt;This is not a flex.&lt;/p&gt;

&lt;p&gt;It’s a clue.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Better Mental Model
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expectation ≠ Reality  
        ↓  
Investigate the gap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s debugging.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The best developers aren’t the ones who write perfect code.&lt;br&gt;
They’re the ones who can &lt;strong&gt;quickly understand why things break&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Debugging isn’t a side skill.&lt;/p&gt;

&lt;p&gt;It &lt;em&gt;is&lt;/em&gt; the job.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>career</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Stripe Connect on Accounts v2 — Standard, Express, and Custom, Rebuilt Around Configurations (with HTTP, Python, C#, and PHP)</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Mon, 06 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/stripe-connect-on-accounts-v2-standard-express-and-custom-rebuilt-around-configurations-with-2ab0</link>
      <guid>https://dev.to/billy_de_cartel/stripe-connect-on-accounts-v2-standard-express-and-custom-rebuilt-around-configurations-with-2ab0</guid>
      <description>&lt;p&gt;This is the &lt;strong&gt;Accounts v2&lt;/strong&gt; companion to the &lt;strong&gt;&lt;a href="https://dev.to/posts/integrating-to-stripe/"&gt;original Connect guide (Accounts v1)&lt;/a&gt;&lt;/strong&gt;. Same &lt;strong&gt;platform concepts&lt;/strong&gt;—Standard, Express, Custom, money flow, compliance—but the &lt;strong&gt;API shape&lt;/strong&gt; is the one Stripe describes in &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/accounts-v2" rel="noopener noreferrer"&gt;Connect and the Accounts v2 API&lt;/a&gt;&lt;/strong&gt;. Use the older post when you must stay on &lt;strong&gt;v1&lt;/strong&gt; (&lt;code&gt;Account.create&lt;/code&gt; with &lt;code&gt;type=express&lt;/code&gt;, OAuth-only Standard flows, etc.). Use &lt;strong&gt;this&lt;/strong&gt; post when you are designing around &lt;strong&gt;one &lt;code&gt;Account&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;configurations&lt;/strong&gt;, and &lt;strong&gt;&lt;code&gt;customer_account&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads-up:&lt;/strong&gt; v2 uses &lt;strong&gt;different endpoints and JSON&lt;/strong&gt; than classic &lt;code&gt;Accounts&lt;/code&gt; CRUD. Always confirm &lt;strong&gt;API version&lt;/strong&gt;, &lt;strong&gt;preview flags&lt;/strong&gt;, and &lt;strong&gt;SDK support&lt;/strong&gt; in &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/accounts-v2" rel="noopener noreferrer"&gt;Stripe’s documentation&lt;/a&gt;&lt;/strong&gt; before shipping production code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is Stripe Connect?
&lt;/h2&gt;

&lt;p&gt;Stripe Connect is for &lt;strong&gt;platforms&lt;/strong&gt; that move money between &lt;strong&gt;buyers&lt;/strong&gt;, &lt;strong&gt;sellers&lt;/strong&gt;, and &lt;strong&gt;your platform&lt;/strong&gt;. You connect &lt;strong&gt;connected accounts&lt;/strong&gt; to your &lt;strong&gt;platform account&lt;/strong&gt; so you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Collect payments from customers&lt;/li&gt;
&lt;li&gt;Pay out to sellers or service providers&lt;/li&gt;
&lt;li&gt;Take &lt;strong&gt;application fees&lt;/strong&gt; where appropriate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stripe holds the &lt;strong&gt;payments&lt;/strong&gt; rail; you own &lt;strong&gt;product and UX&lt;/strong&gt; choices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two ideas at once: “style” (Standard / Express / Custom) and “shape” (v2 configurations)
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;&lt;a href="https://dev.to/posts/integrating-to-stripe/"&gt;original guide&lt;/a&gt;&lt;/strong&gt; compares &lt;strong&gt;Standard&lt;/strong&gt;, &lt;strong&gt;Express&lt;/strong&gt;, and &lt;strong&gt;Custom&lt;/strong&gt; as &lt;strong&gt;who owns onboarding, dashboards, and compliance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accounts v2&lt;/strong&gt; adds a separate axis: &lt;strong&gt;what capabilities&lt;/strong&gt; a single &lt;strong&gt;&lt;code&gt;Account&lt;/code&gt;&lt;/strong&gt; has, expressed as &lt;strong&gt;configurations&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration (v2)&lt;/th&gt;
&lt;th&gt;Role in plain language&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;merchant&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Accept card payments, get paid out—what you usually wanted from a*&lt;em&gt;connected account&lt;/em&gt;* selling on your platform.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;customer&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Be*&lt;em&gt;billed like a customer&lt;/em&gt;* (subscriptions, invoices to your platform) using the &lt;strong&gt;same&lt;/strong&gt; Account identity—often replacing a separate &lt;strong&gt;Customer&lt;/strong&gt; object for that business.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;recipient&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Receive*&lt;em&gt;transfers&lt;/em&gt;* (e.g. indirect charges), using the v2 &lt;strong&gt;transfer&lt;/strong&gt; capabilities Stripe documents for recipients.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can assign &lt;strong&gt;one or more&lt;/strong&gt; configurations to the &lt;strong&gt;same&lt;/strong&gt; &lt;code&gt;Account&lt;/code&gt;. That is the core promise of v2: &lt;strong&gt;one identity&lt;/strong&gt;, &lt;strong&gt;multiple roles&lt;/strong&gt;, &lt;strong&gt;less manual ID mapping&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Standard / Express / Custom&lt;/strong&gt; choice is still &lt;strong&gt;real&lt;/strong&gt;: it describes &lt;strong&gt;OAuth vs Stripe-hosted onboarding vs fully custom UI&lt;/strong&gt;. On v2 you implement those &lt;strong&gt;experiences&lt;/strong&gt; while creating and updating &lt;strong&gt;Accounts&lt;/strong&gt; through the &lt;strong&gt;v2&lt;/strong&gt; surface (where supported).&lt;/p&gt;

&lt;h2&gt;
  
  
  Standard vs Express vs Custom (unchanged product trade-offs)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / aspect&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Standard&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Express&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Custom&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Who owns the Stripe relationship&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The user’s*&lt;em&gt;existing&lt;/em&gt;* Stripe user; you connect via &lt;strong&gt;OAuth&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;You create*&lt;em&gt;connected accounts&lt;/em&gt;&lt;em&gt;; Stripe hosts **onboarding&lt;/em&gt;* and a &lt;strong&gt;light&lt;/strong&gt; dashboard.&lt;/td&gt;
&lt;td&gt;You own*&lt;em&gt;all&lt;/em&gt;* UX; Stripe is invisible to end users.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KYC / onboarding&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;User uses*&lt;em&gt;Stripe Dashboard&lt;/em&gt;*.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Stripe-hosted&lt;/strong&gt; onboarding (Account Links, etc., per current docs).&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;You&lt;/strong&gt; collect data and satisfy Stripe requirements via API.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dashboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full*&lt;em&gt;Stripe&lt;/em&gt;* dashboard for the user.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Express&lt;/strong&gt; dashboard.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;None&lt;/strong&gt; unless you build it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best when&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sellers*&lt;em&gt;already&lt;/em&gt;* have Stripe.&lt;/td&gt;
&lt;td&gt;You need*&lt;em&gt;speed&lt;/em&gt;* and shared compliance.&lt;/td&gt;
&lt;td&gt;You need*&lt;em&gt;full&lt;/em&gt;* branding and control.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On &lt;strong&gt;v2&lt;/strong&gt;, you still make these &lt;strong&gt;product&lt;/strong&gt; choices—but you attach &lt;strong&gt;merchant&lt;/strong&gt; / &lt;strong&gt;customer&lt;/strong&gt; / &lt;strong&gt;recipient&lt;/strong&gt; &lt;strong&gt;configurations&lt;/strong&gt; to match what each connected business must do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architectural overview
&lt;/h2&gt;

&lt;p&gt;Fund flow is unchanged at a high level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  C[Customer] --&amp;gt; P[Your platform]
  P --&amp;gt; A[Connected Account]
  A --&amp;gt; B[Bank payout]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What changes in v2&lt;/strong&gt; is &lt;strong&gt;object modeling&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One &lt;strong&gt;&lt;code&gt;Account&lt;/code&gt;&lt;/strong&gt; can represent &lt;strong&gt;both&lt;/strong&gt; “seller” and “buyer of your SaaS” if you add &lt;strong&gt;&lt;code&gt;merchant&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;customer&lt;/code&gt;&lt;/strong&gt; configurations.&lt;/li&gt;
&lt;li&gt;APIs that took &lt;strong&gt;&lt;code&gt;customer&lt;/code&gt;&lt;/strong&gt; may accept &lt;strong&gt;&lt;code&gt;customer_account&lt;/code&gt;&lt;/strong&gt; with an &lt;strong&gt;Account&lt;/strong&gt; ID—see &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/use-accounts-as-customers" rel="noopener noreferrer"&gt;using Accounts as customers&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Accounts v2 primitives (before code)
&lt;/h2&gt;

&lt;p&gt;Stripe’s v2 &lt;strong&gt;&lt;code&gt;Account&lt;/code&gt;&lt;/strong&gt; objects differ from v1’s flat &lt;code&gt;Account&lt;/code&gt; create. You typically send:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;identity&lt;/code&gt;&lt;/strong&gt; — country, entity type, business details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;configuration&lt;/code&gt;&lt;/strong&gt; — nested blocks such as &lt;strong&gt;&lt;code&gt;merchant&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;customer&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;recipient&lt;/code&gt;&lt;/strong&gt;, each with &lt;strong&gt;capabilities&lt;/strong&gt; you request (&lt;code&gt;card_payments&lt;/code&gt;, balance / payout capabilities as named in current docs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;defaults&lt;/code&gt;&lt;/strong&gt; — currency, locales, responsibilities (fees / losses collector), etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;include&lt;/code&gt;&lt;/strong&gt; — ask the API to return nested sections (e.g. &lt;code&gt;configuration.merchant&lt;/code&gt;, &lt;code&gt;requirements&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Responses may &lt;strong&gt;omit&lt;/strong&gt; fields unless you &lt;strong&gt;&lt;code&gt;include&lt;/code&gt;&lt;/strong&gt; them—see Stripe’s note on &lt;strong&gt;&lt;a href="https://docs.stripe.com/api-includable-response-values" rel="noopener noreferrer"&gt;includable response values&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API version:&lt;/strong&gt; v2 calls often require a specific &lt;strong&gt;&lt;code&gt;Stripe-Version&lt;/code&gt;&lt;/strong&gt; header (including &lt;strong&gt;preview&lt;/strong&gt; versions while the API evolves). Set this from the &lt;strong&gt;exact&lt;/strong&gt; value in &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/accounts-v2" rel="noopener noreferrer"&gt;Stripe’s Accounts v2 docs&lt;/a&gt;&lt;/strong&gt; for your integration window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation: creating an Account on v2 (HTTP-first)
&lt;/h2&gt;

&lt;p&gt;Official examples are &lt;strong&gt;REST + JSON&lt;/strong&gt;. Below is &lt;strong&gt;illustrative&lt;/strong&gt;—copy &lt;strong&gt;field names&lt;/strong&gt;, &lt;strong&gt;capability keys&lt;/strong&gt;, and &lt;strong&gt;version&lt;/strong&gt; from the current docs, not from this blog alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  cURL (canonical pattern from Stripe)
&lt;/h3&gt;

&lt;p&gt;Stripe documents &lt;strong&gt;&lt;code&gt;POST /v2/core/accounts&lt;/code&gt;&lt;/strong&gt; with a JSON body. Conceptually:&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://api.stripe.com/v2/core/accounts &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_test_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Stripe-Version: &amp;lt;version-from-stripe-docs&amp;gt;"&lt;/span&gt; &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;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "contact_email": "seller@example.com",
    "display_name": "Example Seller",
    "identity": {
      "country": "us",
      "entity_type": "company",
      "business_details": { "registered_name": "Example Co" }
    },
    "configuration": {
      "merchant": {
        "capabilities": {
          "card_payments": { "requested": true }
        }
      },
      "customer": {
        "capabilities": {
          "automatic_indirect_tax": { "requested": true }
        }
      }
    },
    "defaults": {
      "currency": "usd",
      "responsibilities": {
        "fees_collector": "stripe",
        "losses_collector": "stripe"
      },
      "locales": ["en-US"]
    },
    "include": [
      "configuration.customer",
      "configuration.merchant",
      "identity",
      "requirements"
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows the &lt;strong&gt;mental model&lt;/strong&gt;: &lt;strong&gt;one&lt;/strong&gt; create, &lt;strong&gt;multiple&lt;/strong&gt; configurations, &lt;strong&gt;explicit&lt;/strong&gt; includes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python (generic HTTP client)
&lt;/h3&gt;

&lt;p&gt;Until your &lt;strong&gt;&lt;code&gt;stripe&lt;/code&gt;&lt;/strong&gt; SDK exposes stable helpers for every v2 path, &lt;strong&gt;&lt;code&gt;httpx&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;requests&lt;/code&gt;&lt;/strong&gt; keeps you aligned with the docs:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_account_v2&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.stripe.com/v2/core/accounts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;STRIPE_SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stripe-Version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;version-from-stripe-docs&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;# ...same structure as the cURL example...
&lt;/span&gt;        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PHP (Laravel-style)
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;Guzzle&lt;/strong&gt; or Laravel &lt;strong&gt;&lt;code&gt;Http::withHeaders([...])-&amp;gt;post(...)&lt;/code&gt;&lt;/strong&gt; with the same URL, &lt;strong&gt;&lt;code&gt;Stripe-Version&lt;/code&gt;&lt;/strong&gt;, and JSON body. Keep &lt;strong&gt;secrets&lt;/strong&gt; in &lt;strong&gt;config&lt;/strong&gt;, not in source control.&lt;/p&gt;

&lt;h3&gt;
  
  
  C# (.NET)
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;&lt;code&gt;HttpClient&lt;/code&gt;&lt;/strong&gt; with &lt;strong&gt;&lt;code&gt;StringContent(json, Encoding.UTF8, "application/json")&lt;/code&gt;&lt;/strong&gt; and the same headers. Deserialize the JSON response into your own DTOs that track &lt;strong&gt;&lt;code&gt;id&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;requirements&lt;/code&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;code&gt;configuration.*&lt;/code&gt;&lt;/strong&gt; state.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;OAuth / Standard accounts:&lt;/strong&gt; Stripe currently directs platforms that authenticate with &lt;strong&gt;OAuth&lt;/strong&gt; to connected accounts to &lt;strong&gt;continue using v1&lt;/strong&gt; for that path. Treat &lt;strong&gt;Standard&lt;/strong&gt; as &lt;strong&gt;documented in the &lt;a href="https://dev.to/posts/integrating-to-stripe/"&gt;v1 guide&lt;/a&gt;&lt;/strong&gt; until your OAuth + v2 story is explicitly supported for your use case—see &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/accounts-v2" rel="noopener noreferrer"&gt;Accounts v2&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://docs.stripe.com/stripe-apps/api-authentication/oauth" rel="noopener noreferrer"&gt;OAuth&lt;/a&gt;&lt;/strong&gt;.&lt;br&gt;
{: .prompt-tip }&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Onboarding links (Express-style flows)
&lt;/h2&gt;

&lt;p&gt;For &lt;strong&gt;Express-like&lt;/strong&gt; experiences you still send users through &lt;strong&gt;Stripe-hosted onboarding&lt;/strong&gt; where the product allows it. With a &lt;strong&gt;connected account ID&lt;/strong&gt; returned from v2 (&lt;code&gt;acct_...&lt;/code&gt;), &lt;strong&gt;&lt;code&gt;Account Links&lt;/code&gt;&lt;/strong&gt; (v1 resource) remain the usual tool for &lt;strong&gt;&lt;code&gt;account_onboarding&lt;/code&gt;&lt;/strong&gt;—the same pattern as the &lt;strong&gt;&lt;a href="https://dev.to/posts/integrating-to-stripe/"&gt;v1 article&lt;/a&gt;&lt;/strong&gt;, but the &lt;strong&gt;&lt;code&gt;account&lt;/code&gt;&lt;/strong&gt; value may come from a &lt;strong&gt;v2&lt;/strong&gt; create. Verify compatibility for your &lt;strong&gt;API version&lt;/strong&gt; in Stripe’s docs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python (v1 Account Links API, account id from v2 create):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;
&lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STRIPE_SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccountLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;connected_account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;refresh_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://yourapp.com/reauth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://yourapp.com/complete&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_onboarding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Custom-style integrations on v2
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Custom&lt;/strong&gt; still means: &lt;strong&gt;you&lt;/strong&gt; own KYC collection, ToS acceptance, and ongoing verification. On v2 you express that by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sending &lt;strong&gt;complete &lt;code&gt;identity&lt;/code&gt;&lt;/strong&gt; data the API requires.&lt;/li&gt;
&lt;li&gt;Requesting &lt;strong&gt;&lt;code&gt;merchant&lt;/code&gt;&lt;/strong&gt; / &lt;strong&gt;&lt;code&gt;recipient&lt;/code&gt;&lt;/strong&gt; capabilities your product needs.&lt;/li&gt;
&lt;li&gt;Handling &lt;strong&gt;&lt;code&gt;requirements&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;webhooks&lt;/strong&gt; the same way you would for Custom on v1—only the &lt;strong&gt;payload shape&lt;/strong&gt; differs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may still use &lt;strong&gt;&lt;code&gt;tos_acceptance&lt;/code&gt;&lt;/strong&gt;-style fields where the v2 schema maps them; follow &lt;strong&gt;Stripe’s v2 reference&lt;/strong&gt; for exact property names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Accounts as customers (&lt;code&gt;customer_account&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Where you used &lt;strong&gt;&lt;code&gt;customer=cus_...&lt;/code&gt;&lt;/strong&gt;, many flows accept &lt;strong&gt;&lt;code&gt;customer_account=acct_...&lt;/code&gt;&lt;/strong&gt; for an Account that has the &lt;strong&gt;&lt;code&gt;customer&lt;/code&gt;&lt;/strong&gt; configuration. Example pattern from Stripe (conceptual):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.stripe.com/v1/setup_intents &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"sk_test_...:"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Stripe-Version: &amp;lt;version-from-stripe-docs&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;customer_account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;acct_123 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"payment_method_types[]=card"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;confirm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;usage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;off_session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Details and supported objects live under &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/use-accounts-as-customers" rel="noopener noreferrer"&gt;using Accounts as customers&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking balances
&lt;/h2&gt;

&lt;p&gt;For many &lt;strong&gt;Connect&lt;/strong&gt; operations, &lt;strong&gt;connected account&lt;/strong&gt; scoping is unchanged. &lt;strong&gt;Python:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stripe_account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;PHP:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;\Stripe\Balance&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'stripe_account'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$accountId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;C#:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BalanceGetOptions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;RequestOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;StripeAccount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accountId&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;Confirm in Stripe’s docs whether your &lt;strong&gt;v2&lt;/strong&gt; account IDs behave identically for every &lt;strong&gt;Balance&lt;/strong&gt; and &lt;strong&gt;v1&lt;/strong&gt; helper you rely on during migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compliance responsibilities
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Standard / Express / Custom&lt;/strong&gt; compliance split from the &lt;strong&gt;&lt;a href="https://dev.to/posts/integrating-to-stripe/"&gt;original article&lt;/a&gt;&lt;/strong&gt; still applies &lt;strong&gt;who&lt;/strong&gt; collects KYC and &lt;strong&gt;who&lt;/strong&gt; owns disputes. &lt;strong&gt;v2&lt;/strong&gt; can &lt;strong&gt;reduce duplicate&lt;/strong&gt; identity collection when you &lt;strong&gt;add&lt;/strong&gt; configurations to an &lt;strong&gt;existing&lt;/strong&gt; Account instead of opening a second &lt;strong&gt;Customer&lt;/strong&gt; record.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;th&gt;Standard&lt;/th&gt;
&lt;th&gt;Express&lt;/th&gt;
&lt;th&gt;Custom&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;KYC&lt;/td&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;Mostly Stripe&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tax reporting&lt;/td&gt;
&lt;td&gt;Stripe-heavy&lt;/td&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;Often you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PCI&lt;/td&gt;
&lt;td&gt;Stripe-hosted elements&lt;/td&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;Mostly you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disputes&lt;/td&gt;
&lt;td&gt;Stripe-heavy&lt;/td&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;Often you&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Choosing a path
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Style to favor&lt;/th&gt;
&lt;th&gt;v2 angle&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sellers already on Stripe; OAuth&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Standard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Often*&lt;em&gt;v1 OAuth&lt;/em&gt;* until Stripe supports your OAuth + v2 plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fast marketplace onboarding&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Express&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;v2&lt;/strong&gt; &lt;code&gt;Account&lt;/code&gt; + &lt;strong&gt;&lt;code&gt;merchant&lt;/code&gt;&lt;/strong&gt; + Account Links&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;White-label, embedded finance&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Custom&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;v2&lt;/strong&gt; full &lt;strong&gt;&lt;code&gt;identity&lt;/code&gt;&lt;/strong&gt; + capabilities + your UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same business pays you*and* sells on your platform&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Express&lt;/strong&gt; or &lt;strong&gt;Custom&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Same*&lt;em&gt;&lt;code&gt;Account&lt;/code&gt;&lt;/em&gt;&lt;em&gt;, *&lt;/em&gt;&lt;code&gt;merchant&lt;/code&gt;** + &lt;strong&gt;&lt;code&gt;customer&lt;/code&gt;&lt;/strong&gt; configurations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Strategic considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time-to-market:&lt;/strong&gt; Standard (when OAuth fits) &amp;lt; Express &amp;lt; Custom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API surface:&lt;/strong&gt; v2 adds &lt;strong&gt;configuration&lt;/strong&gt; discipline—plan for &lt;strong&gt;migration&lt;/strong&gt; from v1, not an eternal split (Stripe &lt;strong&gt;discourages&lt;/strong&gt; maintaining both versions simultaneously). - &lt;a href="https://docs.stripe.com/connect/accounts-v2" rel="noopener noreferrer"&gt;Reference&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDKs:&lt;/strong&gt; Expect to use &lt;strong&gt;HTTP&lt;/strong&gt; for some &lt;strong&gt;v2&lt;/strong&gt; paths until your language SDK is fully aligned.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Accounts v2&lt;/strong&gt; does not erase &lt;strong&gt;Standard / Express / Custom&lt;/strong&gt;—it &lt;strong&gt;repackages&lt;/strong&gt; how you &lt;strong&gt;represent&lt;/strong&gt; connected users in the API. Start from &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/accounts-v2" rel="noopener noreferrer"&gt;Connect and the Accounts v2 API&lt;/a&gt;&lt;/strong&gt;, add &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/use-accounts-as-customers" rel="noopener noreferrer"&gt;using Accounts as customers&lt;/a&gt;&lt;/strong&gt; when the same legal entity both &lt;strong&gt;sells&lt;/strong&gt; and &lt;strong&gt;buys&lt;/strong&gt; from your platform, and keep the &lt;strong&gt;&lt;a href="https://dev.to/posts/integrating-to-stripe/"&gt;v1 Connect guide&lt;/a&gt;&lt;/strong&gt; handy for &lt;strong&gt;OAuth&lt;/strong&gt; flows and &lt;strong&gt;legacy&lt;/strong&gt; snippets until you have fully moved.&lt;/p&gt;

&lt;p&gt;Either way, you still trade off &lt;strong&gt;control&lt;/strong&gt;, &lt;strong&gt;compliance&lt;/strong&gt;, and &lt;strong&gt;complexity&lt;/strong&gt;—only the &lt;strong&gt;object model&lt;/strong&gt; got a long-overdue upgrade.&lt;/p&gt;

</description>
      <category>paymentprocessing</category>
      <category>softwaredevelopment</category>
      <category>stripe</category>
    </item>
    <item>
      <title>Contract Testing: Prevent Breaking Changes Before Production</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Thu, 19 Mar 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/contract-testing-prevent-breaking-changes-before-production-2f44</link>
      <guid>https://dev.to/billy_de_cartel/contract-testing-prevent-breaking-changes-before-production-2f44</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“It worked locally. Tests passed. But production still broke.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’re building distributed systems with multiple services and frontends, you’ve likely encountered this (whether using &lt;strong&gt;.NET + Angular&lt;/strong&gt; , &lt;strong&gt;Node.js + React&lt;/strong&gt; , &lt;strong&gt;Python + Vue&lt;/strong&gt; , or any combination):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A backend change gets deployed&lt;/li&gt;
&lt;li&gt;The frontend suddenly breaks&lt;/li&gt;
&lt;li&gt;No tests warned you&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The issue isn’t always logic.&lt;/p&gt;

&lt;p&gt;It’s often a &lt;strong&gt;broken contract between systems&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Silent API Breakages
&lt;/h2&gt;

&lt;p&gt;In a typical setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service Provider: An API or microservice&lt;/li&gt;
&lt;li&gt;Service Consumer: A frontend, app, or another service&lt;/li&gt;
&lt;li&gt;Communication: JSON over HTTP (or any protocol)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything depends on one thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The consumer and provider agreeing on &lt;strong&gt;what data looks like&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A Real Example
&lt;/h2&gt;

&lt;p&gt;Let’s use a &lt;strong&gt;.NET API&lt;/strong&gt; and &lt;strong&gt;Angular frontend&lt;/strong&gt; as an example (though this applies to any tech stack).&lt;/p&gt;

&lt;h3&gt;
  
  
  API Provider (Initial Version)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserDto&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;




&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{id}"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;GetUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&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="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;UserDto&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Billy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"billy@example.com"&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;h3&gt;
  
  
  Consumer Interface (Angular Example)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&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="nl"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything works perfectly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then a “Small” Change Happens
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserDto&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FullName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// renamed&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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;h3&gt;
  
  
  Production Result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="c1"&gt;// undefined&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The UI breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Didn’t Traditional Tests Catch This?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Unit tests → passed (backend logic is fine)&lt;/li&gt;
&lt;li&gt;Integration tests → passed (they used the old model)&lt;/li&gt;
&lt;li&gt;The API still returns valid JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the &lt;strong&gt;contract between systems changed&lt;/strong&gt; —and traditional tests don’t verify that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Contract Testing?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Contract testing ensures that your service provider always matches what the consumer expects.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A &lt;strong&gt;contract&lt;/strong&gt; defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request format&lt;/li&gt;
&lt;li&gt;Response structure&lt;/li&gt;
&lt;li&gt;Required fields&lt;/li&gt;
&lt;li&gt;Data types&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  In Simple Terms
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;consumer&lt;/strong&gt; (frontend, app, or service) defines expectations The &lt;strong&gt;provider&lt;/strong&gt; (API or service) must satisfy them&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Consumer vs Provider
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Consumer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Calls the service/API&lt;/li&gt;
&lt;li&gt;Defines expected structure&lt;/li&gt;
&lt;li&gt;Examples: Angular frontend, React app, mobile app, another microservice&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Provider
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Returns the data&lt;/li&gt;
&lt;li&gt;Must not break expectations&lt;/li&gt;
&lt;li&gt;Examples: .NET API, Node.js backend, Python service, GraphQL endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  How Contract Testing Works
&lt;/h1&gt;

&lt;p&gt;Instead of relying only on integration tests:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Angular defines expectations&lt;/li&gt;
&lt;li&gt;A contract file is generated&lt;/li&gt;
&lt;li&gt;.NET verifies the contract&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Flow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Consumer Test (e.g., Angular)
      ↓
Generates Contract
      ↓
Saved as JSON/YAML
      ↓
Provider verifies against contract (e.g., .NET API)

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

&lt;/div&gt;



&lt;h1&gt;
  
  
  Practical Example
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;(.NET backend + Angular frontend as an example)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Define Expectations in Consumer (Angular example)
&lt;/h2&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;expectedUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Billy&lt;/span&gt;&lt;span class="dl"&gt;"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;billy@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should fetch user correctly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="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;user&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;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&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="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedUser&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;h2&gt;
  
  
  Generated Contract (Simplified)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/users/1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Billy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billy@example.com"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Verify in Provider (.NET API example)
&lt;/h2&gt;

&lt;p&gt;Install Pact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package PactNet

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Provider Test
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&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;void&lt;/span&gt; &lt;span class="nf"&gt;VerifyPact&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pactVerifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PactVerifier&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;pactVerifier&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UserApi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:5000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithFileSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FileInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pacts/userapi-angular.json"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Verify&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;h2&gt;
  
  
  If Provider Breaks the Contract
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;
&lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FullName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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;ul&gt;
&lt;li&gt;&lt;p&gt;The test fails immediately&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You catch the issue before deployment&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Where Contract Testing Fits
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;E2E Tests
(user journeys)

Integration Tests
(service interactions)

Contract Tests 
(API agreements)

Unit Tests
(business logic)

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

&lt;/div&gt;



&lt;h1&gt;
  
  
  Why This Matters in Real Projects
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Safer Refactoring
&lt;/h2&gt;

&lt;p&gt;Change DTOs, schema, or API responses without fear. Contract tests verify nothing broke.&lt;/p&gt;

&lt;h2&gt;
  
  
  Independent Development
&lt;/h2&gt;

&lt;p&gt;Consumer and provider teams move faster. Changes are caught instantly, not in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Faster Debugging
&lt;/h2&gt;

&lt;p&gt;Failures clearly show what broke&lt;/p&gt;

&lt;h2&gt;
  
  
  Stronger CI/CD Pipelines
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Consumer Build → Generate Contract
Provider Build → Verify Contract
Deploy → Only if both pass

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

&lt;/div&gt;



&lt;p&gt;This works with any tech stack.&lt;/p&gt;

&lt;h1&gt;
  
  
  Common Mistakes
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Over-Specifying Data
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Billy Okeyo"&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing Everything
&lt;/h2&gt;

&lt;p&gt;Only validate fields your frontend actually uses&lt;/p&gt;

&lt;h2&gt;
  
  
  Ignoring Versioning
&lt;/h2&gt;

&lt;p&gt;Breaking contracts without versioning leads to production issues&lt;/p&gt;

&lt;h1&gt;
  
  
  Best Practices
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Keep Contracts Minimal
&lt;/h2&gt;

&lt;p&gt;Focus only on required fields&lt;/p&gt;

&lt;h2&gt;
  
  
  Version Your API
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/api/v1/users
/api/v2/users

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automate in CI/CD
&lt;/h2&gt;

&lt;p&gt;Contracts should be generated and verified automatically&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Realistic Data
&lt;/h2&gt;

&lt;p&gt;Avoid unrealistic mocks&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Takeaway
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Unit tests verify logic Integration tests verify systems E2E tests verify user flows&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contract tests verify agreements&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;“Most production bugs aren’t failures… they’re misunderstandings between systems.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Contract testing eliminates those misunderstandings.&lt;/p&gt;

</description>
      <category>contracttesting</category>
      <category>apitesting</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Why Your Django App Needs Redis and Celery in Production</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Mon, 16 Mar 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/why-your-django-app-needs-redis-and-celery-in-production-4jeg</link>
      <guid>https://dev.to/billy_de_cartel/why-your-django-app-needs-redis-and-celery-in-production-4jeg</guid>
      <description>&lt;p&gt;Django is an incredibly powerful framework for building web applications quickly. However, as your application grows, certain tasks begin to slow down request-response cycles.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sending emails&lt;/li&gt;
&lt;li&gt;Generating reports&lt;/li&gt;
&lt;li&gt;Processing uploaded files&lt;/li&gt;
&lt;li&gt;Running background analytics&lt;/li&gt;
&lt;li&gt;Sending notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running these tasks during an HTTP request can make your application slow and unreliable.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;Celery and Redis&lt;/strong&gt; come in.&lt;/p&gt;

&lt;p&gt;Together they allow you to run background jobs asynchronously without blocking your main application.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Synchronous Tasks
&lt;/h2&gt;

&lt;p&gt;Imagine a user submits a request that triggers an operation that takes &lt;strong&gt;10 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating a financial report&lt;/li&gt;
&lt;li&gt;Parsing a large document&lt;/li&gt;
&lt;li&gt;Sending multiple emails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your application processes this synchronously:&lt;br&gt;
&lt;/p&gt;

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

User Request → Django → Long Task → Response

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

&lt;/div&gt;



&lt;p&gt;The user waits for the entire process to finish.&lt;/p&gt;

&lt;p&gt;This leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow responses&lt;/li&gt;
&lt;li&gt;Poor user experience&lt;/li&gt;
&lt;li&gt;Possible request timeouts&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Introducing Celery
&lt;/h2&gt;

&lt;p&gt;Celery is a &lt;strong&gt;distributed task queue&lt;/strong&gt; that allows you to run background jobs outside the request-response cycle.&lt;/p&gt;

&lt;p&gt;Instead of executing tasks immediately, Django sends the job to a queue.&lt;/p&gt;

&lt;p&gt;A worker then processes it asynchronously.&lt;/p&gt;

&lt;p&gt;The flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
User Request
    ↓
Django
    ↓
Queue Task
    ↓
Immediate Response
    ↓
Celery Worker
    ↓
Executes Job

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

&lt;/div&gt;



&lt;p&gt;This makes your application &lt;strong&gt;fast and scalable&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Redis?
&lt;/h2&gt;

&lt;p&gt;Celery requires a &lt;strong&gt;message broker&lt;/strong&gt; to manage task queues.&lt;/p&gt;

&lt;p&gt;Redis is commonly used because it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extremely fast&lt;/li&gt;
&lt;li&gt;Lightweight&lt;/li&gt;
&lt;li&gt;Easy to deploy&lt;/li&gt;
&lt;li&gt;Perfect for queues and caching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Redis stores the tasks until workers pick them up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example: Sending Email in the Background
&lt;/h2&gt;

&lt;p&gt;Instead of sending email directly in a Django view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;send_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Welcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Thanks for signing up&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;noreply@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&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;You create a Celery task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shared_task&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.core.mail&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_mail&lt;/span&gt;

&lt;span class="nd"&gt;@shared_task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_welcome_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;send_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Welcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Thanks for signing up&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;noreply@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;email&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;Then call it asynchronously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;send_welcome_email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The user gets an immediate response while the email is processed in the background.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Use Cases for Celery
&lt;/h2&gt;

&lt;p&gt;Celery is useful for many production tasks:&lt;/p&gt;

&lt;h3&gt;
  
  
  Email sending
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Welcome emails&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;li&gt;Password resets&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Data processing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Financial calculations&lt;/li&gt;
&lt;li&gt;AI processing&lt;/li&gt;
&lt;li&gt;Data pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Scheduled tasks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Daily reports&lt;/li&gt;
&lt;li&gt;Cleaning expired sessions&lt;/li&gt;
&lt;li&gt;Updating analytics&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Running Celery in Production
&lt;/h2&gt;

&lt;p&gt;A typical Django production stack might 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;Users
  ↓
Nginx
  ↓
Gunicorn
  ↓
Django App
  ↓
Redis (Broker)
  ↓
Celery Workers

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

&lt;/div&gt;



&lt;p&gt;Each component plays a role:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nginx&lt;/strong&gt; handles web traffic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gunicorn&lt;/strong&gt; runs Django&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; manages task queues&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Celery workers&lt;/strong&gt; execute background jobs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Scaling Celery Workers
&lt;/h2&gt;

&lt;p&gt;One of Celery’s biggest advantages is scalability.&lt;/p&gt;

&lt;p&gt;If tasks increase, you simply add more workers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;celery &lt;span class="nt"&gt;-A&lt;/span&gt; project worker &lt;span class="nt"&gt;--loglevel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nt"&gt;--concurrency&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4

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

&lt;/div&gt;



&lt;p&gt;More workers mean faster task processing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Celery and Redis are essential tools for running Django applications at scale.&lt;/p&gt;

&lt;p&gt;They allow you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improve response times&lt;/li&gt;
&lt;li&gt;Handle heavy workloads&lt;/li&gt;
&lt;li&gt;Build scalable architectures&lt;/li&gt;
&lt;li&gt;Run background processing reliably&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your Django application handles tasks that take more than a few seconds, moving them to Celery is one of the best architectural decisions you can make.&lt;/p&gt;

</description>
      <category>django</category>
      <category>redis</category>
      <category>celery</category>
      <category>backgroundtasks</category>
    </item>
    <item>
      <title>A Practical Guide to File Uploads (Images, Excel, CSV) in Angular + Django</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Mon, 23 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/a-practical-guide-to-file-uploads-images-excel-csv-in-angular-django-15gl</link>
      <guid>https://dev.to/billy_de_cartel/a-practical-guide-to-file-uploads-images-excel-csv-in-angular-django-15gl</guid>
      <description>&lt;p&gt;File uploads are one of those features that look simple… until they aren’t.&lt;/p&gt;

&lt;p&gt;Images need previewing. CSV files need parsing. Excel files need validation. Large files need handling. And suddenly your “simple upload” becomes a full feature.&lt;/p&gt;

&lt;p&gt;In this guide, we’ll build a practical and production-ready file upload system using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Angular (Frontend)&lt;/li&gt;
&lt;li&gt;Django + Django REST Framework (Backend)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll cover:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Image uploads with preview&lt;/li&gt;
&lt;li&gt;CSV uploads and parsing&lt;/li&gt;
&lt;li&gt;Excel uploads and processing&lt;/li&gt;
&lt;li&gt;Validation and security best practices&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  Backend Setup (Django + DRF)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Install Dependencies
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;djangorestframework pillow openpyxl pandas

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pillow&lt;/code&gt; → Image processing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;openpyxl&lt;/code&gt; → Excel support&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pandas&lt;/code&gt; → CSV &amp;amp; Excel parsing&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Configure Media Files
&lt;/h2&gt;

&lt;h3&gt;
  
  
  settings.py
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MEDIA_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/media/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;MEDIA_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;media&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  urls.py (project level)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.conf.urls.static&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;static&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# your urls
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MEDIA_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document_root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MEDIA_ROOT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;






&lt;h1&gt;
  
  
  Part 1: Image Upload
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Django Model
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ImageField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upload_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;profiles/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Serializer
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Profile&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfileSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Profile&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; __all__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  View
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIView&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.response&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.parsers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MultiPartParser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FormParser&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfileUploadView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;parser_classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MultiPartParser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FormParser&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProfileSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;serializer&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="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_201_CREATED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_400_BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;






&lt;h1&gt;
  
  
  Angular Frontend (Image Upload)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  HTML
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;(change)=&lt;/span&gt;&lt;span class="s"&gt;"onFileSelected($event)"&lt;/span&gt; &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;"image/*"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"previewUrl"&lt;/span&gt; &lt;span class="na"&gt;[src]=&lt;/span&gt;&lt;span class="s"&gt;"previewUrl"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"upload()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Upload&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Component
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;selectedFile&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;previewUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;ArrayBuffer&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;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;onFileSelected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;selectedFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previewUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsDataURL&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;selectedFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;upload&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;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Billy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image&lt;/span&gt;&lt;span class="dl"&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;selectedFile&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;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:8000/api/upload/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;res&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;h1&gt;
  
  
  Part 2: CSV Upload and Parsing
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Django View for CSV
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIView&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.response&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.parsers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MultiPartParser&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CSVUploadView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;parser_classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MultiPartParser&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FILES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Invalid file type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Example processing
&lt;/span&gt;        &lt;span class="n"&gt;total_rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rows&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;total_rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;columns&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

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

&lt;/div&gt;






&lt;h1&gt;
  
  
  Angular CSV Upload
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;uploadCSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&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;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&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;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:8000/api/upload-csv/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;res&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;h1&gt;
  
  
  Part 3: Excel Upload (.xlsx)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Django View for Excel
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExcelUploadView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;parser_classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MultiPartParser&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FILES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.xlsx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Invalid file type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_excel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rows&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;columns&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&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="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;






&lt;h1&gt;
  
  
  Validation &amp;amp; Security Best Practices
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Limit File Size
&lt;/h2&gt;

&lt;p&gt;In settings.py:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;DATA_UPLOAD_MAX_MEMORY_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5242880&lt;/span&gt; &lt;span class="c1"&gt;# 5MB
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Validate File Type Properly
&lt;/h2&gt;

&lt;p&gt;Don’t rely only on extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;image/jpeg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;image/png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Invalid image format&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Rename Uploaded Files
&lt;/h2&gt;



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

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filename&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uploads/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Handle Large Files with Streaming
&lt;/h2&gt;

&lt;p&gt;For very large CSV files, process in chunks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunksize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# process chunk
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;

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

&lt;/div&gt;






&lt;h1&gt;
  
  
  Bonus: Returning Processed Data
&lt;/h1&gt;

&lt;p&gt;Sometimes you don’t just upload — you process and return a result file.&lt;/p&gt;

&lt;p&gt;Example: Generate processed CSV&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text/csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Disposition&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;attachment; filename=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;processed.csv&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

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

&lt;/div&gt;



&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;File uploads are not just about saving files.&lt;/p&gt;

&lt;p&gt;They are about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;User experience&lt;/li&gt;
&lt;li&gt;Scalability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When done correctly, they become powerful data pipelines inside your application.&lt;/p&gt;

&lt;p&gt;If you’re building admin systems, reporting tools, learning platforms, or fintech dashboards — mastering uploads is essential.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>fullstack</category>
      <category>django</category>
      <category>angular</category>
    </item>
    <item>
      <title>2025 in Review: A Year of Growth, Resilience, and Practical Engineering</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Mon, 29 Dec 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/2025-in-review-a-year-of-growth-resilience-and-practical-engineering-26kd</link>
      <guid>https://dev.to/billy_de_cartel/2025-in-review-a-year-of-growth-resilience-and-practical-engineering-26kd</guid>
      <description>&lt;p&gt;&lt;em&gt;Who would’ve thought I’d open my editor on the 29th of December not to debug production or chase a failing test—but to write this recap? Because apparently, I enjoy explaining bugs to the internet.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you told me at the beginning of 2025 that I’d spend the year writing about &lt;strong&gt;scalable systems, testing strategies, outages, performance optimizations, and developer excuses&lt;/strong&gt; I would’ve believed you. If you also told me I’d still be debugging things that “worked yesterday,” I would’ve believed you even faster.&lt;/p&gt;

&lt;p&gt;This article is a recap of everything I wrote this year: the lessons, the wins, the bugs, and the “how did this even pass code review?” moments. Think of it as a &lt;strong&gt;Spotify Wrapped&lt;/strong&gt; , but for technical articles, minus the judgment (okay, maybe a little).&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;System Design &amp;amp; Scalability — Because Your App &lt;em&gt;Will&lt;/em&gt; Grow (Whether You’re Ready or Not)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; This year, I spent a lot of time talking about &lt;strong&gt;scalable backend systems&lt;/strong&gt; — not because every app needs to handle millions of users, but because &lt;em&gt;every app eventually meets its first “why is the server on fire?” moment&lt;/em&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/billy_de_cartel/how-to-build-scalable-backend-systems-with-python-c-php-and-dart-al0"&gt;How to Build Scalable Backend Systems with Python, C#, PHP and Dart&lt;/a&gt;&lt;/strong&gt;A practical guide to designing systems that won’t collapse the moment your app gets featured on Twitter… or worse, WhatsApp groups.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key lesson:&lt;/strong&gt; Scalability isn’t about overengineering — it’s about &lt;em&gt;not regretting your life choices later&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Testing — Trust Issues, But Make Them Automated&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; Testing dominated a good part of the year, mainly because nothing builds trust issues faster than code that &lt;em&gt;looks correct&lt;/em&gt; but isn’t.&lt;/p&gt;

&lt;p&gt;This series walked through testing from the basics to full CI/CD integration — because “it works on my machine” is not a deployment strategy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/billy_de_cartel/unit-integration-and-end-to-end-tests-building-confidence-in-your-software-1j3"&gt;Unit, Integration, and End-to-End Tests — Part 1&lt;/a&gt;&lt;/strong&gt;Where we learn that tests are not the enemy — flaky tests are.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/billy_de_cartel/unit-integration-and-end-to-end-tests-building-confidence-in-your-software-1j3"&gt;Unit Testing in Depth — Part 2&lt;/a&gt;&lt;/strong&gt;Small tests, big confidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/billy_de_cartel/integration-testing-in-depth-test-components-working-together-and-not-hate-it-part-3-5cmp"&gt;Integration Testing in Depth — Part 3&lt;/a&gt;&lt;/strong&gt;When components finally talk to each other… and start arguing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://billyokeyo.dev/posts/end-to-end-testing/" rel="noopener noreferrer"&gt;End-to-End Testing in Depth — Part 4&lt;/a&gt;&lt;/strong&gt;Testing your app the same way users break it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/billy_de_cartel/the-testing-pyramid-wrapping-up-with-cicd-and-best-practices-part-5-3h8g"&gt;The Testing Pyramid &amp;amp; CI/CD — Part 5&lt;/a&gt;&lt;/strong&gt;The moment you realize automation saves both time &lt;em&gt;and&lt;/em&gt; sanity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key lesson:&lt;/strong&gt; Tests don’t slow you down — debugging production issues does.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Performance &amp;amp; Tooling — Making Code Faster Without Sacrificing Sleep&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; Performance optimization showed up in different forms this year — from modern runtimes to build optimizations. Because sometimes the app isn’t slow… it’s just doing unnecessary work very enthusiastically.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://billyokeyo.dev/posts/webassembly/" rel="noopener noreferrer"&gt;Diving into WebAssembly: What It Is and Why It Matters&lt;/a&gt;&lt;/strong&gt;For when JavaScript alone just isn’t fast enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://billyokeyo.dev/posts/tree-shaking-in-typescript/" rel="noopener noreferrer"&gt;Tree Shaking in TypeScript&lt;/a&gt;&lt;/strong&gt;Removing code you forgot existed but was still shipped to production.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key lesson:&lt;/strong&gt; If users say your app is slow, they’re probably right. The logs just haven’t confessed yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Engineering Culture — Because Code Is Written by Humans (Flawed Ones)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; Not everything this year was serious architecture talk. Some articles leaned into the &lt;em&gt;very human side&lt;/em&gt; of software development — where excuses are plentiful and accountability is… negotiable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/billy_de_cartel/top-10-developer-excuses-when-code-breaks-and-what-actually-went-wrong-1n2i"&gt;Top 10 Developer Excuses When Code Breaks&lt;/a&gt;&lt;/strong&gt;A humorous breakdown of things we’ve all said — and what actually went wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key lesson:&lt;/strong&gt; If you’ve never blamed the cache, the network, or “some weird edge case,” are you even a developer?&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Industry Case Studies — Learning the Hard Way (So You Don’t Have To)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; One of the most impactful pieces this year was breaking down a &lt;strong&gt;real-world outage&lt;/strong&gt; — because nothing teaches engineering humility like watching large systems fail in creative ways.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/billy_de_cartel/what-engineers-can-learn-from-the-cloudflare-outage-november-2025-3m0e"&gt;What Engineers Can Learn From the Cloudflare Outage&lt;/a&gt;&lt;/strong&gt;A reminder that one bad configuration can humble the biggest companies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key lesson:&lt;/strong&gt; Distributed systems don’t fail loudly — they fail &lt;em&gt;politely, globally, and at the worst possible time&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;What 2025 Really Taught Me&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If I had to summarize the year in engineering truths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scalability matters &lt;em&gt;before&lt;/em&gt; users complain.&lt;/li&gt;
&lt;li&gt;Tests are cheaper than apologies.&lt;/li&gt;
&lt;li&gt;Performance issues hide in plain sight.&lt;/li&gt;
&lt;li&gt;Systems fail — preparation determines whether you panic or recover.&lt;/li&gt;
&lt;li&gt;Developers are predictable creatures with unpredictable bugs.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Looking Ahead&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In the next year, expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More real-world case studies&lt;/li&gt;
&lt;li&gt;Deeper dives into distributed systems and cloud patterns&lt;/li&gt;
&lt;li&gt;Practical articles you can actually apply on Monday morning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for reading, sharing, and occasionally finding bugs in my examples (yes, I see you).&lt;/p&gt;

&lt;p&gt;Here’s to another year of writing code, fixing mistakes, and pretending we knew what we were doing all along.&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>yearinreview</category>
    </item>
    <item>
      <title>What Engineers Can Learn From the Cloudflare Outage (November 2025)</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Fri, 21 Nov 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/what-engineers-can-learn-from-the-cloudflare-outage-november-2025-3m0e</link>
      <guid>https://dev.to/billy_de_cartel/what-engineers-can-learn-from-the-cloudflare-outage-november-2025-3m0e</guid>
      <description>&lt;p&gt;&lt;em&gt;How a single oversized configuration file brought down parts of the internet and why this matters for every engineering team.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On November 18, 2025, the internet shook a little.&lt;/p&gt;

&lt;p&gt;Cloudflare, the massive networking and security platform powering millions of websites globally, experienced a major outage that resulted in widespread 5xx errors across the internet. For several hours, key services like CDN routing, Workers KV, Access authentication, and even Cloudflare’s own dashboard were degraded.&lt;/p&gt;

&lt;p&gt;In their official &lt;a href="https://blog.cloudflare.com/18-november-2025-outage/" rel="noopener noreferrer"&gt;&lt;strong&gt;incident report&lt;/strong&gt; ,&lt;/a&gt; Cloudflare broke down exactly what happened, and the root cause was surprisingly small and deceptively simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A configuration file grew larger than expected, violated a hidden assumption in the proxy code, and triggered runtime panics across the global edge.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This incident is a powerful case study in modern distributed systems. Let’s break down what went wrong — and why engineers everywhere should take note.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What Actually Happened? A Chain Reaction From One Oversized File&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The root cause began with a permissions change in Cloudflare’s &lt;strong&gt;ClickHouse database cluster&lt;/strong&gt; , which led to duplicate rows in a dataset used by their Bot Management engine. That duplication caused the generated “feature file”, a config-like file that proxies rely on to double in size.&lt;/p&gt;

&lt;p&gt;Here’s where assumptions came back to haunt them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloudflare’s proxy engine expected a maximum of &lt;strong&gt;200 features&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The new file exceeded that limit.&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;.unwrap()&lt;/code&gt; in their Rust-based FL2 proxy assumed the file would always be valid.&lt;/li&gt;
&lt;li&gt;That assumption failed — the code panicked — resulting in cascading 5xx failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As Cloudflare noted in their report, this caused two types of breakage across their edge:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;FL2 proxies (new engine)&lt;/strong&gt;: Panicked → produced 5xx errors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FL proxies (old engine)&lt;/strong&gt;: Failed to process bot scores → defaulted to zero → broke logic in Access, rules, authentication, and security products&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To make things even more confusing, Cloudflare’s status page (hosted externally) briefly went offline too, creating a misleading early hypothesis that the outage was a &lt;strong&gt;massive DDoS attack&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It wasn’t. It was configuration drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why This Seemingly Small Bug Became a Big Internet Event&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Config files have become “just another part of the deployment pipeline,” especially in cloud platforms where machine-generated metadata drives features. But Cloudflare’s outage shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Config is not static&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Config can be corrupted&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Config needs validation just like code&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because this file was distributed globally across tens of thousands of Cloudflare servers, a single flawed generation step caused a worldwide issue within minutes.&lt;/p&gt;

&lt;p&gt;Distributed systems amplify mistakes — both good ones and bad ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Key Engineering Lessons We Should All Learn&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Never rely on hidden assumptions&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Cloudflare’s proxy code assumed the feature file would never exceed a certain size. That “should never happen” moment is often the birthplace of outages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Add explicit limits, schema checks, and sanity validations to &lt;em&gt;all&lt;/em&gt; config ingestion paths.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;2. Configuration is part of your software supply chain&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The feature file was generated, replicated, and consumed automatically — no human intervention. That makes it just as risky as code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Treat configuration pipelines as first-class citizens: test them, validate them, gate them.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Build for graceful degradation, not hard crashes&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A single &lt;code&gt;.unwrap()&lt;/code&gt; took down parts of the internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Fail softly. If config is invalid, degrade safely, skip rules, or revert to defaults — don’t panic.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Feature flags and kill switches are essential&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Cloudflare themselves acknowledged the need for a more robust kill-switch system in their follow-up plans.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Every modern engineering team should have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;global feature kill switches&lt;/li&gt;
&lt;li&gt;fast configuration rollback&lt;/li&gt;
&lt;li&gt;manual override options&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. Monitoring needs context, not just alarms&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Many engineers watching the outage thought it was an external attack. Alerting didn’t tell them &lt;strong&gt;where&lt;/strong&gt; the failure was coming from, just that everything was “down.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Monitoring should distinguish between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;origin failure&lt;/li&gt;
&lt;li&gt;edge failure&lt;/li&gt;
&lt;li&gt;config failure&lt;/li&gt;
&lt;li&gt;auth failure&lt;/li&gt;
&lt;li&gt;internal propagation issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Context reduces misdiagnosis.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;6. Observability tools must be lightweight during crises&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;During recovery, Cloudflare reported that their debugging systems started consuming high CPU, which slowed other mission-critical services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Your troubleshooting tools &lt;strong&gt;must&lt;/strong&gt; use minimal resources when the system is stressed.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;7. Transparency helps the whole industry learn&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Cloudflare’s detailed post-mortem is a model for engineering culture. Their openness helps engineers worldwide understand real failure modes in large-scale systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Share your failures. They help others avoid the same mistakes.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;The Bigger Picture: Why This Incident Matters&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Cloudflare’s outage wasn’t just a story about a config file. It was a reminder of how fragile the modern internet can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We automate everything&lt;/li&gt;
&lt;li&gt;We trust our pipelines&lt;/li&gt;
&lt;li&gt;We deploy continuously&lt;/li&gt;
&lt;li&gt;We assume schemas won’t change&lt;/li&gt;
&lt;li&gt;We rely on millions of machines making the same decision correctly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But when the assumptions underneath those systems break, failures propagate at machine speed.&lt;/p&gt;

&lt;p&gt;For engineers, SREs, DevOps teams, and platform architects, this incident underscores a fundamental truth:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Your system is only as resilient as your least-validated assumption.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Final Thoughts&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Cloudflare’s outage was a blip in the timeline of the internet, but the lessons are timeless.&lt;/p&gt;

&lt;p&gt;Distributed systems will always fail, the question is how gracefully they fail, how quickly they recover, and how deeply the organization learns from the event.&lt;/p&gt;

&lt;p&gt;Cloudflare’s transparency, analysis, and remediation steps set a strong example for engineering teams everywhere.&lt;/p&gt;

</description>
      <category>engineering</category>
      <category>devops</category>
      <category>sre</category>
      <category>cloudflare</category>
    </item>
    <item>
      <title>Stripe Connect Integration Guide — Standard, Express, and Custom Accounts Explained (with Laravel, C#, and Python Examples)</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Tue, 21 Oct 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/stripe-connect-integration-guide-standard-express-and-custom-accounts-explained-with-laravel-3b5k</link>
      <guid>https://dev.to/billy_de_cartel/stripe-connect-integration-guide-standard-express-and-custom-accounts-explained-with-laravel-3b5k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update (2026):&lt;/strong&gt; Stripe’s &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/accounts-v2" rel="noopener noreferrer"&gt;Accounts v2 API&lt;/a&gt;&lt;/strong&gt; uses a unified &lt;code&gt;Account&lt;/code&gt; with &lt;strong&gt;configurations&lt;/strong&gt; (&lt;code&gt;merchant&lt;/code&gt;, &lt;code&gt;customer&lt;/code&gt;, &lt;code&gt;recipient&lt;/code&gt;) so one identity can sell, pay your platform, and receive transfers without parallel &lt;strong&gt;Customer&lt;/strong&gt; IDs. For a &lt;strong&gt;full guide&lt;/strong&gt; in the same spirit as this article—but aligned to v2—see &lt;strong&gt;&lt;a href="https://dev.to/posts/stripe-connect-accounts-v2/"&gt;Stripe Connect on Accounts v2&lt;/a&gt;&lt;/strong&gt;. &lt;strong&gt;Everything below&lt;/strong&gt; remains the reference for &lt;strong&gt;Accounts v1&lt;/strong&gt; (&lt;code&gt;Account.create&lt;/code&gt;, OAuth, Account Links as shown). V2 though is the recommended path for the new Stripe Connect Integrations.&lt;br&gt;
{: .prompt-tip }&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Stripe offers a powerful ecosystem for building &lt;strong&gt;payment platforms&lt;/strong&gt;, &lt;strong&gt;marketplaces&lt;/strong&gt;, and &lt;strong&gt;financial applications&lt;/strong&gt;.&lt;br&gt;
One of its most powerful products, &lt;strong&gt;Stripe Connect&lt;/strong&gt;, allows you to facilitate payments between multiple parties while maintaining flexibility over &lt;strong&gt;branding, compliance,&lt;/strong&gt; and &lt;strong&gt;user experience&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Choosing between &lt;strong&gt;Standard&lt;/strong&gt;, &lt;strong&gt;Express&lt;/strong&gt;, and &lt;strong&gt;Custom&lt;/strong&gt; accounts is one of the most important architectural decisions you’ll make when designing a payment platform.&lt;br&gt;
This guide combines a &lt;strong&gt;step-by-step tutorial&lt;/strong&gt; and &lt;strong&gt;deep technical analysis&lt;/strong&gt; to help you understand and implement each type.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Is Stripe Connect?
&lt;/h2&gt;

&lt;p&gt;Stripe Connect is designed for &lt;strong&gt;platforms&lt;/strong&gt; that process payments on behalf of others.&lt;br&gt;
It enables you to connect user accounts (called &lt;strong&gt;Connected Accounts&lt;/strong&gt;) to your &lt;strong&gt;Platform Account&lt;/strong&gt; so you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Collect payments from customers&lt;/li&gt;
&lt;li&gt;Distribute payouts to vendors, lenders, or service providers&lt;/li&gt;
&lt;li&gt;Optionally collect platform fees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your &lt;strong&gt;platform never needs to hold funds directly&lt;/strong&gt; (unless you design it to), and Stripe handles the movement of money under the hood.&lt;/p&gt;
&lt;h2&gt;
  
  
  Connected Accounts Overview
&lt;/h2&gt;

&lt;p&gt;Each connected account represents a business, seller, or user on your platform.&lt;br&gt;
Depending on how much &lt;strong&gt;control&lt;/strong&gt; and &lt;strong&gt;responsibility&lt;/strong&gt; you want, you’ll choose between:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Standard&lt;/strong&gt; — Stripe handles almost everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Express&lt;/strong&gt; — Stripe handles compliance, you handle limited UX.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom&lt;/strong&gt; — You handle everything; Stripe is invisible to your users.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / Aspect&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Standard&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Express&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Custom&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ownership of Stripe Account&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;User owns and manages their Stripe account directly.&lt;/td&gt;
&lt;td&gt;User gets a lightweight managed account. Stripe handles UI for onboarding and dashboards.&lt;/td&gt;
&lt;td&gt;Full control; your platform owns the payment experience. Users never see Stripe.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KYC / Onboarding&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Handled entirely by Stripe via the user’s Stripe dashboard.&lt;/td&gt;
&lt;td&gt;Stripe-hosted onboarding flow with minimal setup.&lt;/td&gt;
&lt;td&gt;You collect all KYC data through your own UI and pass it to Stripe’s API.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dashboard Access&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;User uses Stripe’s own dashboard.&lt;/td&gt;
&lt;td&gt;Limited dashboard (Stripe Express Dashboard).&lt;/td&gt;
&lt;td&gt;No dashboard; your app must display balances, transactions, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compliance Responsibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stripe handles everything.&lt;/td&gt;
&lt;td&gt;Stripe handles most KYC and compliance.&lt;/td&gt;
&lt;td&gt;You are responsible for collecting and transmitting KYC data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Control over UI/UX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Minimal; users leave your app to manage payments.&lt;/td&gt;
&lt;td&gt;Moderate; Stripe provides branded but embeddable flows.&lt;/td&gt;
&lt;td&gt;Full; completely white-labeled experience.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ideal Use Case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Marketplaces where users already have Stripe accounts.&lt;/td&gt;
&lt;td&gt;Platforms needing simple onboarding (e.g., gig apps).&lt;/td&gt;
&lt;td&gt;Fintech, lending, or platforms needing deep financial workflows.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Architectural Overview
&lt;/h2&gt;

&lt;p&gt;All three integration types share a common flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer → Your Platform → Connected Account → Bank Payout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, &lt;strong&gt;the control points differ&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With &lt;strong&gt;Standard&lt;/strong&gt;, Stripe owns the UX and dashboard.&lt;/li&gt;
&lt;li&gt;With &lt;strong&gt;Express&lt;/strong&gt;, Stripe hosts onboarding and dashboard, but your platform manages relationships.&lt;/li&gt;
&lt;li&gt;With &lt;strong&gt;Custom&lt;/strong&gt;, your platform builds everything: UI, onboarding, payouts, and reporting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step-by-Step Implementation by Account Type
&lt;/h2&gt;

&lt;p&gt;Let’s look at how to implement each account type with &lt;strong&gt;C#&lt;/strong&gt;, &lt;strong&gt;Laravel (PHP)&lt;/strong&gt; and &lt;strong&gt;Python&lt;/strong&gt; examples and understand their implications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Standard Accounts — Easiest Setup, Minimal Control
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Concept
&lt;/h3&gt;

&lt;p&gt;Standard accounts are the simplest to integrate.&lt;br&gt;
Users connect &lt;strong&gt;their own existing Stripe accounts&lt;/strong&gt; using OAuth. Stripe handles compliance, payouts, and reporting. Your platform receives a small &lt;strong&gt;application fee&lt;/strong&gt; from each transaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Case
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Ideal for marketplaces or SaaS platforms where users already have Stripe accounts and prefer to manage their own dashboards.&lt;br&gt;
{: .prompt-tip }&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;h4&gt;
  
  
  C
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;accountLink&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccountLinks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AccountLinkCreateOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Account&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;connectedAccountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;RefreshUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://your-platform.com/reauth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ReturnUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://your-platform.com/success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"account_onboarding"&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;h4&gt;
  
  
  Laravel (PHP)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$accountLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;\Stripe\AccountLink&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'account'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$connectedAccountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'refresh_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stripe.reauth'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'return_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stripe.success'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'account_onboarding'&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;h4&gt;
  
  
  Python
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;
&lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk_test_...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;account_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccountLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;connected_account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;refresh_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-platform.com/reauth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-platform.com/success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_onboarding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pros and Cons
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minimal setup&lt;/li&gt;
&lt;li&gt;Stripe handles everything (KYC, compliance, payouts)&lt;/li&gt;
&lt;li&gt;Reduced liability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limited branding control&lt;/li&gt;
&lt;li&gt;Users must leave your platform to manage payments&lt;/li&gt;
&lt;li&gt;Harder to create a unified experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Express Accounts — Fast Onboarding, Shared Control
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Concept
&lt;/h3&gt;

&lt;p&gt;Express accounts strike a balance between simplicity and control.&lt;br&gt;
You manage account creation and linking, but Stripe handles onboarding and provides a &lt;strong&gt;lightweight dashboard&lt;/strong&gt; for your users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Case
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Perfect for gig or service platforms (like driver or freelancer apps) where quick onboarding and basic payout visibility are key.&lt;br&gt;
{: .prompt-tip }&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;h4&gt;
  
  
  C
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AccountCreateOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"express"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Country&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"user@example.com"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccountLinks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AccountLinkCreateOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Account&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;RefreshUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://yourapp.com/reauth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ReturnUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://yourapp.com/complete"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"account_onboarding"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Laravel (PHP)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;\Stripe\Account&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'express'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'country'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'US'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;\Stripe\AccountLink&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'account'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$account&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'refresh_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stripe.reauth'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'return_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'stripe.success'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'account_onboarding'&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;h4&gt;
  
  
  Python
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;express&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccountLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;refresh_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://yourapp.com/reauth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://yourapp.com/complete&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_onboarding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pros and Cons
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster onboarding&lt;/li&gt;
&lt;li&gt;Stripe handles compliance and verification&lt;/li&gt;
&lt;li&gt;Users get access to payout history via the Express dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limited customization&lt;/li&gt;
&lt;li&gt;Stripe branding remains visible&lt;/li&gt;
&lt;li&gt;Less flexibility over reporting or custom payout logic&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Custom Accounts — Full Control, Full Responsibility
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Concept
&lt;/h3&gt;

&lt;p&gt;Custom accounts are designed for &lt;strong&gt;white-labeled&lt;/strong&gt; platforms.&lt;br&gt;
Your app controls &lt;strong&gt;everything&lt;/strong&gt;: onboarding, KYC collection, balances, and payouts.&lt;br&gt;
Stripe is completely invisible to the end user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Case
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Ideal for fintech, embedded finance, lending, or any system that requires deep integration and custom user experiences.&lt;br&gt;
{: .prompt-tip }&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;h4&gt;
  
  
  C
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;accountOptions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AccountCreateOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"custom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Country&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;Capabilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AccountCapabilitiesOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;CardPayments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AccountCapabilitiesCardPaymentsOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Requested&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;Transfers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AccountCapabilitiesTransfersOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Requested&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;TosAcceptance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AccountTosAcceptanceOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Date&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUnixTimeSeconds&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;Ip&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoteIpAddress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accountOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Laravel (PHP)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;\Stripe\Account&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'custom'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'country'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'US'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;strtolower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="s1"&gt;'capabilities'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'card_payments'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'requested'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;'transfers'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'requested'&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="s1"&gt;'tos_acceptance'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'date'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="s1"&gt;'ip'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;ip&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;h4&gt;
  
  
  Python
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;country&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;card_payments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requested&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transfers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requested&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;tos_acceptance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remote_addr&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;h3&gt;
  
  
  Fund Flow Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------+
|             Platform (You)           |
|    No direct money handling          |
+--------------------+-----------------+
                     |
                     ▼
          Create &amp;amp; Manage Connected Accounts
                     |
   +--------------------------------------+
   |     Connected Account (Business)     |
   |  💵 Has Stripe balance, bank info    |
   |  🧾 Disburses and collects funds     |
   +--------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Money Movement&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IN:&lt;/strong&gt; Customer → Connected Account (via ACH/Card)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OUT:&lt;/strong&gt; Connected Account → Bank (payouts)&lt;/li&gt;
&lt;li&gt;Platform orchestrates, Stripe moves the money&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Checking Balances
&lt;/h2&gt;

&lt;h4&gt;
  
  
  Python
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stripe_account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Laravel (PHP)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;\Stripe\Balance&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'stripe_account'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$accountId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  C
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BalanceGetOptions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;RequestOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;StripeAccount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accountId&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;h2&gt;
  
  
  Compliance Responsibilities
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;th&gt;Standard&lt;/th&gt;
&lt;th&gt;Express&lt;/th&gt;
&lt;th&gt;Custom&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;KYC&lt;/td&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tax Reporting&lt;/td&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PCI Compliance&lt;/td&gt;
&lt;td&gt;Stripe-hosted&lt;/td&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;Mostly you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dispute Handling&lt;/td&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Branding&lt;/td&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Fully yours&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; For Custom accounts, implement &lt;strong&gt;Stripe Identity&lt;/strong&gt;, &lt;strong&gt;webhooks&lt;/strong&gt;, and &lt;strong&gt;Stripe Radar&lt;/strong&gt; to automate verification and fraud detection.&lt;br&gt;
{: .prompt-tip }&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Choosing the Right Integration Type
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Recommended Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Marketplace with existing Stripe users&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Standard&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platform needing fast onboarding&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Express&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fintech, lending, or white-labeled finance&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Custom&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Strategic Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time-to-market:&lt;/strong&gt; Standard &amp;lt; Express &amp;lt; Custom&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance load:&lt;/strong&gt; Stripe-heavy → Custom-heavy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branding control:&lt;/strong&gt; Minimal → Full&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revenue potential:&lt;/strong&gt; Low (Standard) → High (Custom)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Every Stripe Connect integration represents a trade-off between &lt;strong&gt;control&lt;/strong&gt;, &lt;strong&gt;compliance&lt;/strong&gt;, and &lt;strong&gt;complexity&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standard&lt;/strong&gt; — easiest to deploy, lowest control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Express&lt;/strong&gt; — balanced control and simplicity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom&lt;/strong&gt; — ultimate flexibility with greater responsibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your goal is &lt;strong&gt;speed&lt;/strong&gt;, start with Express.&lt;br&gt;
If your goal is &lt;strong&gt;brand control and scalability&lt;/strong&gt;, build with Custom.&lt;/p&gt;

&lt;p&gt;Either way, Stripe Connect gives you a future-proof foundation for managing payments, onboarding users, and creating rich financial experiences all through powerful APIs available across languages.&lt;/p&gt;

</description>
      <category>paymentprocessing</category>
      <category>softwaredevelopment</category>
      <category>stripe</category>
    </item>
    <item>
      <title>The Testing Pyramid: Wrapping Up with CI/CD and Best Practices - Part 5</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Fri, 19 Sep 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/the-testing-pyramid-wrapping-up-with-cicd-and-best-practices-part-5-3h8g</link>
      <guid>https://dev.to/billy_de_cartel/the-testing-pyramid-wrapping-up-with-cicd-and-best-practices-part-5-3h8g</guid>
      <description>&lt;p&gt;Software testing is more than writing a few unit tests and hoping for the best. To deliver reliable software, teams need a &lt;strong&gt;balanced testing strategy&lt;/strong&gt; — one that combines &lt;strong&gt;Unit, Integration, and End-to-End (E2E) tests&lt;/strong&gt; in the right proportions. This is where the &lt;strong&gt;Testing Pyramid&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;In this final part of our testing series, we’ll:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand the &lt;strong&gt;Testing Pyramid model&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Explore how to &lt;strong&gt;balance different types of tests&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Learn how to &lt;strong&gt;integrate testing into CI/CD pipelines&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Review &lt;strong&gt;best practices&lt;/strong&gt; for building a sustainable testing culture.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Testing Pyramid Explained
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Testing Pyramid&lt;/strong&gt; (popularized by Mike Cohn) is a metaphor for structuring automated tests:&lt;br&gt;
&lt;/p&gt;

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

        ▲
        | End-to-End (fewest, slowest)
        |
        | Integration (moderate amount, balanced)
        |
        | Unit Tests (largest base, fastest)
        ▼

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Unit Tests (Foundation)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast, cheap, and precise&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Cover small pieces of logic in isolation.&lt;/li&gt;
&lt;li&gt;Should make up &lt;strong&gt;60–70%&lt;/strong&gt; of your automated test suite.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Example: Testing a function that calculates interest rates.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Integration Tests (Middle Layer)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validate how components interact&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Cover database queries, API requests, or service orchestration.&lt;/li&gt;
&lt;li&gt;Should make up &lt;strong&gt;20–30%&lt;/strong&gt; of your test suite.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Example: Ensuring your API endpoint correctly fetches data from the database and formats the response.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. End-to-End Tests (Top)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simulate real user flows&lt;/strong&gt; across the full stack.&lt;/li&gt;
&lt;li&gt;Catch bugs unit or integration tests can’t.&lt;/li&gt;
&lt;li&gt;Should make up &lt;strong&gt;10–15%&lt;/strong&gt; of your suite (because they’re slow and expensive).&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Example: A test where a user logs in, adds a product to their cart, and completes checkout.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Integrating Tests into CI/CD Pipelines
&lt;/h2&gt;

&lt;p&gt;Automated tests are only valuable if they’re &lt;strong&gt;run consistently&lt;/strong&gt;. Modern software teams embed them into CI/CD pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Run Unit Tests Early
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Execute on &lt;strong&gt;every commit or pull request&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Fail fast: block merges if unit tests fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Run Integration Tests on Build/Deploy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Run after the app compiles successfully.&lt;/li&gt;
&lt;li&gt;Can use a &lt;strong&gt;containerized test environment&lt;/strong&gt; with mock or staging databases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Run End-to-End Tests on Staging
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Triggered before production deployment.&lt;/li&gt;
&lt;li&gt;Some teams run &lt;strong&gt;smoke E2E tests&lt;/strong&gt; in production (carefully) to ensure critical flows still work.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Example CI/CD Flow (GitHub Actions / GitLab / Jenkins):&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;jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install dependencies
        run: npm install
      - name: Run unit tests
        run: npm test -- --unit
      - name: Run integration tests
        run: npm test -- --integration
      - name: Run e2e tests
        run: npm run test:e2e

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Practices for a Balanced Testing Strategy
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Follow the Pyramid, Not the Ice Cream Cone
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Too many E2E tests = &lt;strong&gt;slow, brittle pipeline&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Too few unit tests = &lt;strong&gt;shaky foundation&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Balance is key.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Use Test Doubles Wisely
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mocks/Stubs&lt;/strong&gt; in unit tests to isolate dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal mocking&lt;/strong&gt; in integration tests — rely on real services where possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Make Tests Deterministic
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tests should &lt;strong&gt;always produce the same result&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Avoid flaky tests (caused by race conditions, timeouts, or external dependencies).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Keep Tests Fast
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Aim for &lt;strong&gt;seconds, not minutes&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Developers won’t run slow tests locally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Automate Everything in CI/CD
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Manual testing has its place, but regression checks must be automated.&lt;/li&gt;
&lt;li&gt;CI/CD pipelines should be your &lt;strong&gt;safety net&lt;/strong&gt; before production.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Monitor and Improve Test Coverage
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Coverage isn’t everything, but low coverage usually indicates gaps.&lt;/li&gt;
&lt;li&gt;Focus on &lt;strong&gt;critical paths&lt;/strong&gt; rather than chasing 100%.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. Treat Tests as Code
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tests should be &lt;strong&gt;maintainable, reviewed, and refactored&lt;/strong&gt; like production code.&lt;/li&gt;
&lt;li&gt;Avoid “test rot” where tests become outdated or ignored.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The journey from &lt;strong&gt;unit → integration → E2E&lt;/strong&gt; tests gives you &lt;strong&gt;confidence&lt;/strong&gt; at every level of your system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit tests&lt;/strong&gt; keep your building blocks solid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration tests&lt;/strong&gt; ensure the blocks fit together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E2E tests&lt;/strong&gt; confirm the entire structure works as intended.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By following the &lt;strong&gt;Testing Pyramid&lt;/strong&gt; , integrating tests into &lt;strong&gt;CI/CD pipelines&lt;/strong&gt; , and practicing discipline in writing effective tests, you’ll achieve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster feedback loops.&lt;/li&gt;
&lt;li&gt;Fewer production bugs.&lt;/li&gt;
&lt;li&gt;Higher developer confidence.&lt;/li&gt;
&lt;li&gt;Happier end-users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Testing isn’t just about catching bugs, it’s about building &lt;strong&gt;trustworthy software&lt;/strong&gt; at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next Steps for You:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Audit your current test suite.&lt;/li&gt;
&lt;li&gt;See if you’re over-relying on one type of test.&lt;/li&gt;
&lt;li&gt;Gradually reshape your suite into a &lt;strong&gt;pyramid&lt;/strong&gt; , not an ice cream cone.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>testing</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>End-to-End (E2E) Testing in Depth - Part 4</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Fri, 12 Sep 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/end-to-end-e2e-testing-in-depth-part-4-j71</link>
      <guid>https://dev.to/billy_de_cartel/end-to-end-e2e-testing-in-depth-part-4-j71</guid>
      <description>&lt;p&gt;If &lt;strong&gt;unit tests&lt;/strong&gt; check individual functions, and &lt;strong&gt;integration tests&lt;/strong&gt; check how those functions work together, &lt;strong&gt;end-to-end (E2E) tests&lt;/strong&gt; take it all the way: They simulate &lt;strong&gt;real user workflows&lt;/strong&gt; from start to finish, ensuring the entire application stack works as expected.&lt;/p&gt;

&lt;p&gt;E2E testing is about validating the &lt;strong&gt;system as a whole&lt;/strong&gt; , covering everything from frontend to backend, database, APIs, and sometimes even external services.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are E2E Tests?
&lt;/h2&gt;

&lt;p&gt;E2E tests replicate &lt;strong&gt;user behavior&lt;/strong&gt; and verify that the system responds correctly. Think of them as automated users interacting with your app.&lt;/p&gt;

&lt;p&gt;Example workflows tested in E2E:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User logs in → Dashboard loads correctly.&lt;/li&gt;
&lt;li&gt;User creates a record → It’s stored in DB → Appears in the UI.&lt;/li&gt;
&lt;li&gt;Checkout flow in an e-commerce app → Product added → Payment processed → Order confirmed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why E2E Tests Matter
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validate Real Workflows&lt;/strong&gt; → Ensure the system behaves like users expect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catch System-Wide Failures&lt;/strong&gt; → Config issues, routing errors, DB schema mismatches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test Critical Paths&lt;/strong&gt; → Login, payments, onboarding, search, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provide Business Confidence&lt;/strong&gt; → Stakeholders see features working as intended.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Characteristics of E2E Tests
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High Coverage&lt;/strong&gt; – Test across the full stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slowest of All Tests&lt;/strong&gt; – Often run in CI/CD pipelines, not during active coding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brittle&lt;/strong&gt; – Small UI changes can break tests, so balance is key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mimic Real User Behavior&lt;/strong&gt; – Often browser-driven or API-driven.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Side-by-Side Examples
&lt;/h1&gt;

&lt;p&gt;Scenario:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A user visits a web app, logs in, and sees their profile page with their name.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Python (Selenium + pytest)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# test_e2e_login.py
from selenium import webdriver
from selenium.webdriver.common.by import By
import pytest

@pytest.fixture
def driver():
    driver = webdriver.Chrome()
    yield driver
    driver.quit()

def test_login_flow(driver):
    driver.get("http://localhost:5000/login")

    driver.find_element(By.NAME, "username").send_keys("alice")
    driver.find_element(By.NAME, "password").send_keys("password123")
    driver.find_element(By.ID, "login-button").click()

    profile_name = driver.find_element(By.ID, "profile-name").text
    assert profile_name == "Alice"

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  C# (Selenium + xUnit)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;using Xunit;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

public class LoginE2ETests
{
    [Fact]
    public void Login_ShowsProfilePage()
    {
        using var driver = new ChromeDriver();
        driver.Navigate().GoToUrl("http://localhost:5000/login");

        driver.FindElement(By.Name("username")).SendKeys("alice");
        driver.FindElement(By.Name("password")).SendKeys("password123");
        driver.FindElement(By.Id("login-button")).Click();

        var profileName = driver.FindElement(By.Id("profile-name")).Text;
        Assert.Equal("Alice", profileName);
    }
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  TypeScript (Playwright / Cypress)
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;(Playwright example below — Cypress would be very similar)&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// login.e2e.test.ts
import { test, expect } from "@playwright/test";

test("user can login and see profile page", async ({ page }) =&amp;gt; {
  await page.goto("http://localhost:3000/login");

  await page.fill("input[name=username]", "alice");
  await page.fill("input[name=password]", "password123");
  await page.click("#login-button");

  const profileName = await page.textContent("#profile-name");
  expect(profileName).toBe("Alice");
});

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  PHP (Laravel Dusk for browser automation)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// tests/Browser/LoginTest.php
&amp;lt;?php

namespace Tests\Browser;

use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class LoginTest extends DuskTestCase
{
    public function testUserCanLoginAndSeeProfile()
    {
        $this-&amp;gt;browse(function (Browser $browser) {
            $browser-&amp;gt;visit('/login')
                    -&amp;gt;type('username', 'alice')
                    -&amp;gt;type('password', 'password123')
                    -&amp;gt;press('Login')
                    -&amp;gt;assertSee('Alice');
        });
    }
}

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

&lt;/div&gt;



&lt;h1&gt;
  
  
  Comparison Table
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;E2E Testing Goal&lt;/th&gt;
&lt;th&gt;Python (Selenium)&lt;/th&gt;
&lt;th&gt;C# (Selenium + xUnit)&lt;/th&gt;
&lt;th&gt;TypeScript (Playwright/Cypress)&lt;/th&gt;
&lt;th&gt;PHP (Laravel Dusk)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full user workflow (UI → backend → DB)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Selenium&lt;/td&gt;
&lt;td&gt;Selenium&lt;/td&gt;
&lt;td&gt;Playwright/Cypress&lt;/td&gt;
&lt;td&gt;Dusk (browser automation)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slow (browser interaction)&lt;/td&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium/Fast&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Confidence&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Highest — validates the system end-to-end&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;td&gt;⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Login flows, payments, user journeys&lt;/td&gt;
&lt;td&gt;UI + API flows&lt;/td&gt;
&lt;td&gt;UI + API flows&lt;/td&gt;
&lt;td&gt;Fullstack web apps&lt;/td&gt;
&lt;td&gt;Laravel apps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  Best Practices for Writing Effective E2E Tests
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Test Critical User Journeys Only 

&lt;ul&gt;
&lt;li&gt;Focus on login, checkout, payments, onboarding, etc.&lt;/li&gt;
&lt;li&gt;Don’t waste time E2E testing every small feature.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Keep Tests Deterministic 

&lt;ul&gt;
&lt;li&gt;Avoid flakiness by controlling test data.&lt;/li&gt;
&lt;li&gt;Seed databases with known test records.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use Test-Specific Environments 

&lt;ul&gt;
&lt;li&gt;Run E2E tests in staging or CI environments.&lt;/li&gt;
&lt;li&gt;Isolate from production data and services.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Clean Up After Tests 

&lt;ul&gt;
&lt;li&gt;Ensure created records (users, orders) are rolled back or deleted.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use IDs and Stable Selectors 

&lt;ul&gt;
&lt;li&gt;Avoid brittle selectors like div:nth-child(3).&lt;/li&gt;
&lt;li&gt;Use unique IDs or data-test attributes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Parallelize and Optimize 

&lt;ul&gt;
&lt;li&gt;Run tests in parallel (e.g., Playwright, Cypress).&lt;/li&gt;
&lt;li&gt;Keep them minimal to avoid bloated CI runs.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Combine with Unit &amp;amp; Integration Tests &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don’t rely solely on E2E.&lt;/li&gt;
&lt;li&gt;Use a &lt;strong&gt;test pyramid&lt;/strong&gt; strategy:&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Monitor and Review Regularly &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;E2E tests can get brittle.&lt;/li&gt;
&lt;li&gt;Review and refactor when the UI or workflows change.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Key Takeaways
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;E2E tests replicate the user’s journey&lt;/strong&gt; and validate the full system.&lt;/li&gt;
&lt;li&gt;They are &lt;strong&gt;slower and more brittle&lt;/strong&gt; , but they give the &lt;strong&gt;highest level of confidence&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Use them sparingly for &lt;strong&gt;critical paths&lt;/strong&gt; (login, payments, core workflows).&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Balance your test pyramid:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose the right tools for your stack (Selenium, Playwright, Cypress, Dusk).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Follow best practices to keep tests reliable and maintainable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>testing</category>
      <category>e2etesting</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Integration Testing in Depth : Test components working together (and not hate it) Part 3</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Fri, 05 Sep 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/integration-testing-in-depth-test-components-working-together-and-not-hate-it-part-3-5cmp</link>
      <guid>https://dev.to/billy_de_cartel/integration-testing-in-depth-test-components-working-together-and-not-hate-it-part-3-5cmp</guid>
      <description>&lt;p&gt;Integration tests sit in the sweet spot between tiny, fast unit tests and slow, expensive end-to-end tests. They verify that multiple parts of your system cooperate correctly e.g., your API layer talks to the DB the way you expect, background jobs persist state, or your service correctly handles responses from an external API.&lt;/p&gt;

&lt;p&gt;This post is a practical, language-agnostic guide to integration testing, plus &lt;strong&gt;side-by-side, runnable patterns&lt;/strong&gt; for &lt;strong&gt;Python&lt;/strong&gt; , &lt;strong&gt;C# (.NET)&lt;/strong&gt;, &lt;strong&gt;TypeScript (Node/Express)&lt;/strong&gt; and &lt;strong&gt;PHP (Laravel)&lt;/strong&gt; so you can immediately apply the ideas in your stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  What integration tests are (and are not)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Integration tests&lt;/strong&gt; verify behavior across component boundaries, multiple classes, modules, services or infrastructure pieces that would not be exercised by a unit test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They are not:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A replacement for unit tests (they’re slower &amp;amp; coarser).&lt;/li&gt;
&lt;li&gt;Full UI-driven E2E tests (unless you intentionally include the UI).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;They are good for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verifying DB reads/writes via your data access layer.&lt;/li&gt;
&lt;li&gt;Testing service-to-service interactions.&lt;/li&gt;
&lt;li&gt;Ensuring message queue jobs and workers together produce expected state.&lt;/li&gt;
&lt;li&gt;Checking how your app handles external API payloads (with a mock or stub of that API).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Goals &amp;amp; tradeoffs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Goals&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Catch bugs that only appear when components are wired together.&lt;/li&gt;
&lt;li&gt;Validate API contracts inside your own system.&lt;/li&gt;
&lt;li&gt;Give more realistic coverage than unit tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tradeoffs&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slower than unit tests.&lt;/li&gt;
&lt;li&gt;Harder to make fully deterministic (external services, timing).&lt;/li&gt;
&lt;li&gt;Need careful setup/teardown to stay reliable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Core patterns &amp;amp; recommendations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Use realistic but controlled dependencies
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Prefer a &lt;strong&gt;real database&lt;/strong&gt; (or the same engine, e.g., PostgreSQL) rather than mocking DB calls.&lt;/li&gt;
&lt;li&gt;For external services (payment gateways, email providers), use &lt;strong&gt;service doubles&lt;/strong&gt; : a local mock server, WireMock, or HTTP interceptors (nock, responses, Http::fake). Don’t call the live service in CI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Isolate tests
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Run each test in a transaction and roll it back (if possible), or recreate schema between tests.&lt;/li&gt;
&lt;li&gt;Or give each test its own ephemeral database (unique DB name/per-worker) when running tests in parallel.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Keep tests focused
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Each integration test should exercise a meaningful interaction or flow (e.g., API -&amp;gt; DB, or API -&amp;gt; external-service-stub -&amp;gt; DB), not every possible path.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Seed deterministic test data
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use builders/fixtures to create known state. Avoid random data unless seeded.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Manage long-running processes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;For queues/workers, either run workers synchronously in tests, use a fake queue, or spin up a test worker process in CI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Use testcontainers or docker-compose in CI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;For close-to-production fidelity, use Testcontainers (or docker-compose) to provision real DBs and services in CI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. Avoid flaky tests
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;No sleeps/time-based races. Use blocking signals, polling with timeouts, or deterministic stubs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to mock vs when to use real services
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real DB&lt;/strong&gt; : Prefer real DB engine (Postgres, MySQL). SQLite is OK for many cases but can mask engine-specific issues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External APIs&lt;/strong&gt; : Mock in integration tests. Use contract testing (Pact) to keep mocked expectations in-sync.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caches/Queues&lt;/strong&gt; : Use in-memory or test doubles unless you must validate the actual middleware behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Observability: make debugging failing integration tests easy
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Emit structured logs during tests (include request IDs).&lt;/li&gt;
&lt;li&gt;Capture and print responses and DB state on failure.&lt;/li&gt;
&lt;li&gt;Keep helpful assertion messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checklist: test lifecycle
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create test environment (DB, migrations applied).&lt;/li&gt;
&lt;li&gt;Seed minimal deterministic data.&lt;/li&gt;
&lt;li&gt;Execute action via real interfaces (HTTP client, direct call).&lt;/li&gt;
&lt;li&gt;Assert state persisted, side effects happened (e.g., DB row created, message pushed, HTTP call stubbed).&lt;/li&gt;
&lt;li&gt;Tear down (transaction rollback, truncate tables, drop DB).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Common pitfalls &amp;amp; fixes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flaky tests&lt;/strong&gt; : avoid sleep-based waiting; use retry-with-timeout polling and assert deterministically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow setup&lt;/strong&gt; : keep per-test setup minimal; use transactional rollback where possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel test collisions&lt;/strong&gt; : give tests separate DBs or use unique table prefixes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Works locally but fails in CI”&lt;/strong&gt; : mirror CI environment locally using Docker/Testcontainers and run tests there.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Example integration tests (side-by-side)
&lt;/h1&gt;

&lt;p&gt;Scenario used across examples:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A &lt;code&gt;POST /users&lt;/code&gt; endpoint that creates a user record in the database and triggers an HTTP call to an external email service (welcome email). Integration test will create a user via HTTP, verify DB row exists, and verify the email call was made (mocked).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Python — FastAPI + SQLAlchemy + pytest + requests-mock
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt; Use &lt;code&gt;sqlite:///:memory:&lt;/code&gt; or a Testcontainers Postgres for higher fidelity in CI. &lt;code&gt;requests-mock&lt;/code&gt; stubs outgoing HTTP calls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# app.py (pseudo)
from fastapi import FastAPI, Depends
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

app = FastAPI()

# App factory to pass different DB URLs in tests
def create_app(db_url, email_service_url):
    engine = create_engine(db_url)
    Session = sessionmaker(bind=engine)
    # create tables...
    app.state.db = Session
    app.state.email_url = email_service_url

    @app.post("/users")
    def create_user(payload: dict):
        sess = app.state.db()
        user = User(name=payload["name"], email=payload["email"])
        sess.add(user); sess.commit()
        # Send welcome email via requests.post(app.state.email_url, json=...)
        return {"id": user.id}

    return app


# test_integration.py
import pytest
from fastapi.testclient import TestClient
import requests_mock
from app import create_app

@pytest.fixture
def client(tmp_path):
    # Use sqlite in-memory for speed or file DB for persistence across app
    app = create_app("sqlite:///:memory:", "http://email.test/send")
    client = TestClient(app)
    yield client

def test_create_user_and_send_email(client):
    with requests_mock.Mocker() as m:
        m.post("http://email.test/send", status_code=200, json={"ok": True})
        resp = client.post("/users", json={"name":"Alice","email":"a@example.com"})
        assert resp.status_code == 200
        user_id = resp.json()["id"]

        # Verify DB row exists (open a session)
        Session = client.app.state.db
        sess = Session()
        user = sess.query(User).filter_by(id=user_id).one_or_none()
        assert user is not None
        assert user.email == "a@example.com"

        # Verify external email call occurred
        assert m.called
        assert m.request_history[0].json() == {"to": "a@example.com", "template": "welcome"}

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tips&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For CI, replace sqlite with Testcontainers Postgres: &lt;code&gt;create_app(postgres_url, ...)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use DB migrations in setup if using a real DB.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  C# (.NET) — ASP.NET Core + WebApplicationFactory + InMemory DB / Testcontainer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt; Use &lt;code&gt;WebApplicationFactory&amp;lt;TEntryPoint&amp;gt;&lt;/code&gt; to spin the app in tests and override service registrations for test doubles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// In Startup.cs, app reads EmailService via IEmailService (HttpEmailService in prod)

public class TestEmailService : IEmailService {
    public List&amp;lt;EmailMessage&amp;gt; Sent = new();
    public Task SendAsync(EmailMessage msg) { Sent.Add(msg); return Task.CompletedTask; }
}

// Integration test
public class UsersIntegrationTests : IClassFixture&amp;lt;WebApplicationFactory&amp;lt;Program&amp;gt;&amp;gt; {
    private readonly WebApplicationFactory&amp;lt;Program&amp;gt; _factory;

    public UsersIntegrationTests(WebApplicationFactory&amp;lt;Program&amp;gt; factory) {
        _factory = factory.WithWebHostBuilder(builder =&amp;gt; {
            builder.ConfigureServices(services =&amp;gt; {
                // Replace real DB with in-memory or Testcontainer; replace email service with TestEmailService
                services.AddSingleton&amp;lt;IEmailService, TestEmailService&amp;gt;();
                // Configure EF Core to use InMemoryDatabase or connection string from Testcontainers
            });
        });
    }

    [Fact]
    public async Task PostUsers_CreatesUser_And_SendsEmail() {
        var client = _factory.CreateClient();
        var content = new StringContent("{\"name\":\"Bob\",\"email\":\"b@ex.com\"}", Encoding.UTF8, "application/json");
        var resp = await client.PostAsync("/users", content);
        resp.EnsureSuccessStatusCode();

        // Verify DB: use scope to resolve DbContext
        using(var scope = _factory.Services.CreateScope()) {
            var db = scope.ServiceProvider.GetRequiredService&amp;lt;AppDbContext&amp;gt;();
            var user = db.Users.Single(u =&amp;gt; u.Email == "b@ex.com");
            Assert.NotNull(user);
        }

        // Verify TestEmailService captured message
        var emailService = _factory.Services.GetRequiredService&amp;lt;IEmailService&amp;gt;() as TestEmailService;
        Assert.Single(emailService.Sent);
        Assert.Equal("welcome", emailService.Sent[0].Template);
    }
}

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tips&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For a real DB in CI, use &lt;code&gt;Testcontainers&lt;/code&gt; .NET to spin up Postgres and set EF Core connection string.&lt;/li&gt;
&lt;li&gt;Overriding services avoids brittle HTTP stubbing, and keeps assertions in-process.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TypeScript (Node/Express) — supertest + sqlite in-memory + nock
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt; &lt;code&gt;supertest&lt;/code&gt; issues HTTP requests to your Express app instance. Use &lt;code&gt;nock&lt;/code&gt; to intercept outgoing HTTP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// app.ts (pseudo)
import express from "express";
import bodyParser from "body-parser";
import { initDb } from "./db";

export function createApp(dbPath: string) {
  const app = express();
  app.use(bodyParser.json());
  const db = initDb(dbPath); // e.g., sqlite3 in-memory or file
  app.post("/users", async (req, res) =&amp;gt; {
    const { name, email } = req.body;
    const id = await db.run("INSERT INTO users(name,email) VALUES(?,?)", [name, email]);
    // call external email service via fetch/http client
    await fetch("http://email.test/send", { method: "POST", body: JSON.stringify({ to: email, template: "welcome" }) });
    res.json({ id });
  });
  return app;
}


// test/integration.test.ts
import request from "supertest";
import nock from "nock";
import { createApp } from "../app";
import { openDb, getUserById } from "../db";

describe("POST /users", () =&amp;gt; {
  it("creates a user and calls email service", async () =&amp;gt; {
    const app = createApp(":memory:");
    const email = nock("http://email.test")
      .post("/send", (body) =&amp;gt; body.to === "c@ex.com" &amp;amp;&amp;amp; body.template === "welcome")
      .reply(200, { ok: true });

    const res = await request(app)
      .post("/users")
      .send({ name: "Carol", email: "c@ex.com" })
      .expect(200);

    // verify DB
    const user = await getUserById(res.body.id);
    expect(user.email).toBe("c@ex.com");

    // verify external call was made
    expect(email.isDone()).toBe(true);
  });
});

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tips&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For complex schemas, use migrations in test setup or run a dedicated test DB with &lt;code&gt;sqlite&lt;/code&gt; file per test.&lt;/li&gt;
&lt;li&gt;For Postgres in CI, spin up DB via docker-compose or Testcontainers Node.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  PHP (Laravel) — HTTP tests + RefreshDatabase + Http::fake()
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt; Laravel has excellent integration testing helpers. &lt;code&gt;RefreshDatabase&lt;/code&gt; runs migrations and transacts where possible. Use &lt;code&gt;Http::fake()&lt;/code&gt; to intercept external HTTP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// routes/api.php
Route::post('/users', [UserController::class, 'store']);

// UserController-&amp;gt;store uses User model and Http::post('http://email.test/send', ...);


// tests/Feature/CreateUserTest.php
&amp;lt;?php
namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
use App\Models\User;

class CreateUserTest extends TestCase
{
    use RefreshDatabase;

    public function test_create_user_and_send_welcome_email()
    {
        Http::fake([
            'email.test/*' =&amp;gt; Http::response(['ok' =&amp;gt; true], 200),
        ]);

        $response = $this-&amp;gt;postJson('/api/users', [
            'name' =&amp;gt; 'Dan',
            'email' =&amp;gt; 'd@ex.com',
        ]);

        $response-&amp;gt;assertStatus(200);

        // verify DB
        $this-&amp;gt;assertDatabaseHas('users', ['email' =&amp;gt; 'd@ex.com']);

        // assert that an outbound call was made
        Http::assertSent(function ($request) {
            return $request-&amp;gt;url() == 'http://email.test/send' &amp;amp;&amp;amp;
                   $request['to'] == 'd@ex.com' &amp;amp;&amp;amp;
                   $request['template'] == 'welcome';
        });
    }
}

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tips&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel’s &lt;code&gt;RefreshDatabase&lt;/code&gt; will use in-memory sqlite if configured, otherwise migrate a test DB.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;Queue::fake()&lt;/code&gt; to test that jobs were dispatched without executing background workers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Practical integration testing strategies
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Use transactions for isolation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Wrap each test in a DB transaction and roll back at the end. Works well when everything runs in the same DB connection.&lt;/li&gt;
&lt;li&gt;Caveat: some ORMs/connections (e.g., tests that spawn separate processes) might not share transaction visibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use in-memory DBs for speed, but test on real DBs in CI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;SQLite in-memory is fast, but can behave differently (indexing, SQL dialect). Complement local tests with real-engine tests in CI using containers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stubbing external HTTP reliably
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Python: &lt;code&gt;responses&lt;/code&gt; or &lt;code&gt;requests-mock&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Node: &lt;code&gt;nock&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;C#: WireMock.Net or replace typed &lt;code&gt;HttpClient&lt;/code&gt; with test handler&lt;/li&gt;
&lt;li&gt;PHP: Laravel &lt;code&gt;Http::fake()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Testcontainers — real dependencies in CI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Spin up a Postgres, Redis, or Kafka container for integration tests.&lt;/li&gt;
&lt;li&gt;Testcontainers exists for many ecosystems (Java, .NET, Node, Python wrappers).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Contract testing for cross-team APIs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use Pact or similar to generate contracts from consumer tests and verify provider compliance in provider CI. This avoids brittle mocks and catches breaking API changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Background jobs &amp;amp; queues
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Either run job handlers inline (synchronously) in tests, use fake queues that record enqueued messages, or run a worker process in CI that reads from test queue.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  How many integration tests should you write?
&lt;/h1&gt;

&lt;p&gt;No fixed number. Aim for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unit tests: many (business logic)&lt;/li&gt;
&lt;li&gt;Integration tests: enough to cover &lt;strong&gt;critical integration points&lt;/strong&gt; (DB persistence, payment flows, auth)&lt;/li&gt;
&lt;li&gt;E2E tests: few (critical user paths)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A practical rule: write integration tests for &lt;strong&gt;each major DB operation and for the essential external integrations&lt;/strong&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Debugging failing integration tests
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Print request/response bodies and DB rows on failure.&lt;/li&gt;
&lt;li&gt;Capture network traffic (or enable higher logging).&lt;/li&gt;
&lt;li&gt;Reproduce the failing test locally with the same CI container setup (Testcontainers makes that easy).&lt;/li&gt;
&lt;li&gt;If a test is flaky, add instrumentation and increase visibility; temporary retries mask real issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Sample integration test checklist
&lt;/h1&gt;

&lt;p&gt;Before merging an integration test into CI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test uses deterministic data.&lt;/li&gt;
&lt;li&gt;DB schema/migrations run and are applied in setup.&lt;/li&gt;
&lt;li&gt;External dependencies are stubbed or provided by test containers.&lt;/li&gt;
&lt;li&gt;Test cleans up (transaction rollback or truncation).&lt;/li&gt;
&lt;li&gt;No sleeps or time-based races.&lt;/li&gt;
&lt;li&gt;Test is focused on behavior, not implementation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Wrapping up &amp;amp; next steps
&lt;/h1&gt;

&lt;p&gt;Integration tests reduce the mismatch between isolated units and the full system. They give higher confidence than unit tests while being cheaper and faster than full E2E tests. When designed well they catch boundary problems early and make refactoring safer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next post in the series:&lt;/strong&gt; &lt;em&gt;End-to-End (E2E) Testing in Depth&lt;/em&gt; — we’ll cover realistic end-to-end strategies, UI-driving vs API-only E2E, test environments, flaky UI tests, and how to design low-maintenance high-value E2E checks.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Happy testing!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>integrationtesting</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
