<?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: Mostafijur Rahman</title>
    <description>The latest articles on DEV Community by Mostafijur Rahman (@mostafij).</description>
    <link>https://dev.to/mostafij</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%2F640934%2Ff155e6a1-8374-4a04-8177-45c072ac7a62.jpeg</url>
      <title>DEV Community: Mostafijur Rahman</title>
      <link>https://dev.to/mostafij</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mostafij"/>
    <language>en</language>
    <item>
      <title>I Added One-Time Purchases to My React Native App with RevenueCat — Here's Everything I Wish I Knew</title>
      <dc:creator>Mostafijur Rahman</dc:creator>
      <pubDate>Sat, 06 Jun 2026 03:38:11 +0000</pubDate>
      <link>https://dev.to/mostafij/i-added-one-time-purchases-to-my-react-native-app-with-revenuecat-heres-everything-i-wish-i-knew-41hn</link>
      <guid>https://dev.to/mostafij/i-added-one-time-purchases-to-my-react-native-app-with-revenuecat-heres-everything-i-wish-i-knew-41hn</guid>
      <description>&lt;p&gt;Most "in-app purchase" tutorials for React Native assume you want subscriptions. I didn't.&lt;/p&gt;

&lt;p&gt;For my app, I wanted something simpler: pay once, unlock Pro forever. No recurring billing, no churn dashboards, no "your trial is ending" emails. Just a clean lifetime unlock.&lt;/p&gt;

&lt;p&gt;Turns out, doing this &lt;em&gt;well&lt;/em&gt; with RevenueCat in an Expo app has a few sharp edges nobody warned me about. This is the guide I wish I'd had — the actual setup, the code that worked, and the one mistake that cost me an afternoon.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Stack for this guide: React Native + Expo (managed workflow with dev builds), &lt;code&gt;react-native-purchases&lt;/code&gt;, RevenueCat dashboard, and Google Play Console.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why one-time instead of subscription?
&lt;/h2&gt;

&lt;p&gt;I'll be honest — subscriptions make more money on paper. But for the kind of app I was building, a recurring charge felt wrong. People don't want to "subscribe" to a small utility; they want to buy it and move on.&lt;/p&gt;

&lt;p&gt;So the model became: a single &lt;strong&gt;lifetime Pro&lt;/strong&gt; purchase. In RevenueCat terms, that means a &lt;strong&gt;non-consumable&lt;/strong&gt; product tied to one entitlement called &lt;code&gt;pro&lt;/code&gt;. Get that mental model right and everything else falls into place.&lt;/p&gt;

&lt;p&gt;A quick vocabulary check, because mixing these up is where most confusion starts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consumable&lt;/strong&gt; — can be bought repeatedly (coins, power-ups). &lt;em&gt;Not what we want.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-consumable&lt;/strong&gt; — bought once, owned forever (lifetime unlock). &lt;strong&gt;This is us.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscription&lt;/strong&gt; — recurring access. Not us either.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you accidentally configure your lifetime product as a consumable, RevenueCat will treat it as something the user "uses up," and restoring it later gets messy. Set it as non-consumable from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Configure the product in Google Play Console
&lt;/h2&gt;

&lt;p&gt;Before touching any code, the product has to exist on the store side.&lt;/p&gt;

&lt;p&gt;In Play Console, go to &lt;strong&gt;Monetize → Products → In-app products&lt;/strong&gt; (not Subscriptions — that trips people up). Create a new product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Product ID&lt;/strong&gt;: something stable like &lt;code&gt;pro_lifetime&lt;/code&gt;. You can't change this later, so pick carefully.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name &amp;amp; description&lt;/strong&gt;: what the user sees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Price&lt;/strong&gt;: set your launch price.&lt;/li&gt;
&lt;li&gt;Activate it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the store-level setup. The product now exists, but your app doesn't know about it yet — that's what RevenueCat connects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Wire up RevenueCat
&lt;/h2&gt;

&lt;p&gt;In the RevenueCat dashboard:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a &lt;strong&gt;Project&lt;/strong&gt; and add your Android app with its package name.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Entitlements&lt;/strong&gt;, create one called &lt;code&gt;pro&lt;/code&gt;. This is the thing your code checks — "does this user have Pro?"&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Products&lt;/strong&gt;, add the &lt;code&gt;pro_lifetime&lt;/code&gt; product ID you created in Play Console.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;Offerings&lt;/strong&gt;, create a default offering and attach a package containing that product.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The layering feels redundant at first (product → package → offering → entitlement), but it pays off: you can change pricing, swap products, or run experiments later &lt;em&gt;without shipping an app update&lt;/em&gt;. Everything is driven from the dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Install the SDK in Expo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx expo &lt;span class="nb"&gt;install &lt;/span&gt;react-native-purchases
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because this needs native code, you can't run it in Expo Go — you need a &lt;strong&gt;development build&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx expo prebuild
npx expo run:android
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've never made a dev build before, this is the part that surprises Expo developers. Expo Go won't cut it for IAP. Once your dev build is running, you're ready to initialize.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Initialize and fetch offerings
&lt;/h2&gt;

&lt;p&gt;Initialize RevenueCat once, early in your app's lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Purchases&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-purchases&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initRevenueCat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Purchases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your_google_play_api_key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// from RevenueCat dashboard&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 fetch what's available to sell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getOffering&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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;offerings&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;Purchases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOfferings&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;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;offerings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&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;current&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;availablePackages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&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;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;availablePackages&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="c1"&gt;// our lifetime package&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to fetch offerings:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&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="kc"&gt;null&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;Notice you're never hardcoding prices in the app. The price string comes from the store, localized to the user's region automatically. That's one of the quiet wins of going through RevenueCat instead of rolling your own billing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Make the purchase
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buyPro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;customerInfo&lt;/span&gt; &lt;span class="p"&gt;}&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;Purchases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;purchasePackage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pkg&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;customerInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entitlements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pro&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Pro unlocked — update your UI / state here&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userCancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Purchase failed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key check is &lt;code&gt;customerInfo.entitlements.active.pro&lt;/code&gt;. If that entitlement is active, the user owns Pro. Note the &lt;code&gt;e.userCancelled&lt;/code&gt; guard — users backing out of the purchase sheet is normal, not an error worth logging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Check entitlement on every launch
&lt;/h2&gt;

&lt;p&gt;A purchase isn't a one-time event you forget about. Every time the app starts, you check whether this user already owns Pro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isPro&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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;customerInfo&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;Purchases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCustomerInfo&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;customerInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entitlements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pro&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And give users a &lt;strong&gt;Restore Purchases&lt;/strong&gt; button — this is non-negotiable for non-consumables, and both stores expect it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;restore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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;customerInfo&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;Purchases&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restorePurchases&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;customerInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entitlements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pro&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, a user who reinstalls or switches devices loses access to something they paid for — and you'll get a one-star review for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gotcha that cost me an afternoon
&lt;/h2&gt;

&lt;p&gt;Here's the part the tutorials skip.&lt;/p&gt;

&lt;p&gt;When you connect RevenueCat to Google Play, you need to set up a &lt;strong&gt;Google Cloud service account&lt;/strong&gt; so RevenueCat can verify purchases server-side. I hit a "Credentials need attention" warning on the dashboard and went looking for help.&lt;/p&gt;

&lt;p&gt;I asked an AI assistant how to fix it. The steps it gave me were confidently wrong — outdated permission names, a flow that didn't match what I was actually seeing in Google Cloud. I burned real time following instructions for a UI that no longer existed.&lt;/p&gt;

&lt;p&gt;What actually fixed it: going to &lt;strong&gt;RevenueCat's own documentation&lt;/strong&gt; and following their service-account setup step by step. The official docs matched the current Google Cloud console exactly, and the warning cleared.&lt;/p&gt;

&lt;p&gt;The lesson isn't "AI is useless" — I use it constantly. The lesson is that for &lt;strong&gt;platform setup that changes frequently&lt;/strong&gt; (cloud consoles, billing permissions, store configs), the vendor's own docs are the source of truth. AI training data lags behind these UIs by months, and IAP plumbing is exactly the kind of thing that gets reorganized often.&lt;/p&gt;

&lt;p&gt;If you hit the credentials warning: don't improvise, don't trust a generic answer. Open the RevenueCat docs page for Google service account setup and follow it line by line.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few smaller things I learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sandbox testing works before credentials are fully green.&lt;/strong&gt; The "Credentials need attention" warning didn't block me from testing purchases in sandbox, which is why it's easy to ignore — right up until you go to production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a real test account&lt;/strong&gt; added in Play Console's license testing, not your developer account, to test the full purchase flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't hardcode the price&lt;/strong&gt; anywhere in your UI. Read it from the package. Saves you a release when you change pricing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;A one-time lifetime purchase in React Native comes down to four things: configure a non-consumable product, map it to a &lt;code&gt;pro&lt;/code&gt; entitlement in RevenueCat, drive the purchase from offerings, and check the entitlement on launch. The SDK does the heavy lifting.&lt;/p&gt;

&lt;p&gt;The only part that genuinely tripped me up was the Google Cloud credentials linking — and the fix there was simply trusting the official docs over a confident-but-stale answer. If you're building something similar, that's the hour I'd save you.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>revenuecat</category>
      <category>mobile</category>
    </item>
    <item>
      <title>I Thought Domain-Driven Design Was a Waste of Time. I Was Wrong.</title>
      <dc:creator>Mostafijur Rahman</dc:creator>
      <pubDate>Fri, 22 May 2026 10:33:11 +0000</pubDate>
      <link>https://dev.to/mostafij/i-thought-domain-driven-design-was-a-waste-of-time-i-was-wrong-cmk</link>
      <guid>https://dev.to/mostafij/i-thought-domain-driven-design-was-a-waste-of-time-i-was-wrong-cmk</guid>
      <description>&lt;p&gt;The first time someone explained &lt;strong&gt;Domain-Driven Design (DDD)&lt;/strong&gt; to me, I thought it was a lot of ceremony for very little payoff.&lt;/p&gt;

&lt;p&gt;Aggregates, value objects, bounded contexts — a whole vocabulary to learn. I had shipped plenty of features without any of it. My honest assumption: this will just slow me down.&lt;/p&gt;

&lt;p&gt;I was wrong. Here's the short version of how I found out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The project that changed my mind
&lt;/h2&gt;

&lt;p&gt;I worked on a system with a hierarchy — Site, then Aggregator, then a unit under that. A "plan" existed at every level.&lt;/p&gt;

&lt;p&gt;In the code, all three were just &lt;code&gt;plan&lt;/code&gt;. Same name, same shared object, everywhere.&lt;/p&gt;

&lt;p&gt;It worked — until it didn't. Features started colliding. A change to one level's plan quietly broke another. Every function needed a comment explaining &lt;em&gt;which&lt;/em&gt; plan it meant. New people asked the same question every week: "is this the site plan or the unit plan?"&lt;/p&gt;

&lt;p&gt;The code ran fine. The understanding around it was rotting.&lt;/p&gt;

&lt;p&gt;That's the exact problem DDD is built to fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  What DDD actually is
&lt;/h2&gt;

&lt;p&gt;DDD is &lt;strong&gt;not a framework&lt;/strong&gt;. Nothing to install. It's a discipline: design your software around the &lt;em&gt;business problem&lt;/em&gt;, not the database or the framework.&lt;/p&gt;

&lt;p&gt;The patterns get all the attention, but the real value is in two boring ideas.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Ubiquitous language — name things honestly
&lt;/h3&gt;

&lt;p&gt;Pick precise words and use them &lt;em&gt;everywhere&lt;/em&gt; — in conversations, tickets, and the code itself.&lt;/p&gt;

&lt;p&gt;If the business has a "draft plan" and a "submitted plan," those exact words belong in your class names. Not &lt;code&gt;plan&lt;/code&gt; with a vague &lt;code&gt;status&lt;/code&gt; field. The moment a name is vague, logic starts hiding inside it.&lt;/p&gt;

&lt;p&gt;My entire mess traced back to one overloaded word.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Bounded context — stop forcing one model
&lt;/h3&gt;

&lt;p&gt;"Customer" means different things to billing, support, and analytics. Forcing one shared &lt;code&gt;Customer&lt;/code&gt; class gives you 40 fields nobody dares touch.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;bounded context&lt;/strong&gt; is a line you draw: inside this boundary, a word means exactly one thing. Cross the line, you're allowed a different model.&lt;/p&gt;

&lt;p&gt;This is also the most reliable way to answer "how do I split this microservice?" — split where the domain actually splits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The patterns, in one line each
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Entity&lt;/strong&gt; — has a stable identity over time (an order stays that order).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value object&lt;/strong&gt; — defined only by its values (money, a date range).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregate&lt;/strong&gt; — a group of objects with one entry point that enforces the rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repository&lt;/strong&gt; — hides how data is stored from your domain code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain event&lt;/strong&gt; — something meaningful happened (&lt;code&gt;PlanSubmitted&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But these only make sense &lt;em&gt;after&lt;/em&gt; the language and boundary work. Reach for them first and you get fancy code that models nothing real.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to skip DDD
&lt;/h2&gt;

&lt;p&gt;My old skepticism wasn't wrong — just aimed at the wrong project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip DDD if:&lt;/strong&gt; the app is simple CRUD, the project is small or short-lived, or the complexity is technical (algorithms, performance) rather than business logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use DDD if:&lt;/strong&gt; the domain is genuinely complex and the software has to live and evolve for years.&lt;/p&gt;

&lt;p&gt;"This will slow me down" is correct — for simple projects. It's completely wrong for complex ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell my earlier self
&lt;/h2&gt;

&lt;p&gt;The patterns are not the point. You can memorize aggregates and value objects and still build a mess.&lt;/p&gt;

&lt;p&gt;The point is the boring stuff: talk to the people who understand the business, agree on exact words, draw honest boundaries, and let the code follow.&lt;/p&gt;

&lt;p&gt;DDD is overhead. On a simple project, bad trade. On a complex one meant to last, it's the cheapest insurance you'll ever buy.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://mrsajib.com" rel="noopener noreferrer"&gt;mrsajib.com&lt;/a&gt;. I write weekly about backend engineering and building software that lasts.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>AI Will Replace Backend Developers? A Python Engineer's Honest Take</title>
      <dc:creator>Mostafijur Rahman</dc:creator>
      <pubDate>Thu, 07 May 2026 16:29:47 +0000</pubDate>
      <link>https://dev.to/mostafij/ai-will-replace-backend-developers-a-python-engineers-honest-take-551p</link>
      <guid>https://dev.to/mostafij/ai-will-replace-backend-developers-a-python-engineers-honest-take-551p</guid>
      <description>&lt;p&gt;Everyone is talking about AI replacing developers.&lt;/p&gt;

&lt;p&gt;Some people are scared. Some people laugh it off. Most people just don't know what to believe.&lt;/p&gt;

&lt;p&gt;I've been a backend engineer for 6 years. I write Python every day. I build APIs, real-time systems, and production backends. And honestly? I was worried too — until I actually started paying attention to what's happening on the ground.&lt;/p&gt;

&lt;p&gt;Let me share what I've seen. No hype. No fear. Just real experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  Yes, I Use AI Tools Every Day
&lt;/h2&gt;

&lt;p&gt;I'm not going to pretend I don't use AI. I use GitHub Copilot and Claude Code daily.&lt;/p&gt;

&lt;p&gt;They help me move faster. Repetitive tasks that used to take 30 minutes now take 5. Boilerplate code, basic CRUD, writing tests — AI handles these really well.&lt;/p&gt;

&lt;p&gt;But here's the thing I noticed early on:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I never just trust what AI gives me.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every single time, I review it. I test it. I make sure it actually fits the system I'm building — not just some generic example from the internet. Because AI doesn't know &lt;em&gt;my&lt;/em&gt; system. Only I do.&lt;/p&gt;

&lt;p&gt;That one habit — reviewing AI output instead of blindly copying it — is what separates engineers who grow with AI from those who get burned by it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Night My Production API Crashed
&lt;/h2&gt;

&lt;p&gt;Let me tell you about a night I'll never forget.&lt;/p&gt;

&lt;p&gt;It was around 2 AM. My client messaged me — the server was down. Production. Real users. Real impact.&lt;/p&gt;

&lt;p&gt;I jumped on my laptop immediately.&lt;/p&gt;

&lt;p&gt;I checked the logs. Dug through the errors. And there it was — a concurrency bug. Multiple requests were hitting the same database row at the same time, causing a race condition that Django wasn't handling cleanly.&lt;/p&gt;

&lt;p&gt;I fixed it using &lt;code&gt;select_for_update()&lt;/code&gt; — a database-level lock that made sure only one request could modify that row at a time. Crisis over.&lt;/p&gt;

&lt;p&gt;Now here's my question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Could AI have fixed that at 2 AM?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Maybe AI could have suggested &lt;code&gt;select_for_update()&lt;/code&gt; if I described the problem perfectly. But finding the problem in the first place? Understanding &lt;em&gt;why&lt;/em&gt; that specific bug was happening in &lt;em&gt;that&lt;/em&gt; specific system under &lt;em&gt;that&lt;/em&gt; specific load? That took 6 years of experience. That took knowing the codebase. That took staying calm under pressure and thinking clearly when everything is on fire.&lt;/p&gt;

&lt;p&gt;No AI tool was going to wake up and fix that for my client. I was.&lt;/p&gt;




&lt;h2&gt;
  
  
  What AI Actually Cannot Do
&lt;/h2&gt;

&lt;p&gt;After 6 years of real production work, here is what I have learned AI genuinely struggles with:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Understanding your business context&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When I build a system, I need to understand &lt;em&gt;why&lt;/em&gt; it needs to work a certain way — not just &lt;em&gt;how&lt;/em&gt;. AI doesn't sit in client meetings. It doesn't ask the right questions. It doesn't know that your client's team works across time zones and needs very specific workflows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Making architecture decisions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PostgreSQL or DynamoDB? Microservice or monolith? WebSocket or polling? These decisions have consequences that last years. They depend on team size, budget, and where the product is going. AI can give you options — but it cannot make the right call for your specific situation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugging complex production issues&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Race conditions. Memory leaks. Concurrency bugs at 2 AM. These are not problems you solve by copy-pasting into ChatGPT. They need deep system knowledge, real experience, and sometimes — a lot of patience when everything is breaking at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Taking responsibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When production breaks, someone has to own it. Lead the fix. Talk to the client. Make sure it never happens again. AI tools have zero accountability. They cannot be on-call. They cannot look a client in the eye and say "I've got this."&lt;/p&gt;




&lt;h2&gt;
  
  
  So What Is Really Happening?
&lt;/h2&gt;

&lt;p&gt;Here is my honest take:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI is not replacing backend developers. It is replacing developers who refuse to grow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Developers who only write basic APIs with no systems thinking? They will struggle.&lt;/p&gt;

&lt;p&gt;Developers who understand distributed systems, concurrency, data modeling, and business logic — and also know how to use AI tools effectively? They are becoming more valuable than ever.&lt;/p&gt;

&lt;p&gt;The data backs this up. Software engineering job postings in 2026 are at a 3-year high. Companies are not hiring fewer engineers — they are hiring &lt;em&gt;better&lt;/em&gt; ones.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Danger Is Not AI
&lt;/h2&gt;

&lt;p&gt;The real danger is staying the same while everything around you changes.&lt;/p&gt;

&lt;p&gt;If you are still writing the same code the same way you did 3 years ago — that is the actual threat to your career.&lt;/p&gt;

&lt;p&gt;The engineers winning right now are the ones who:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use AI to move faster — not as a replacement for thinking&lt;/li&gt;
&lt;li&gt;Build deep expertise in systems design and architecture&lt;/li&gt;
&lt;li&gt;Understand the business, not just the code&lt;/li&gt;
&lt;li&gt;Communicate clearly when things go wrong&lt;/li&gt;
&lt;li&gt;Never stop learning&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  My Honest Advice
&lt;/h2&gt;

&lt;p&gt;Use AI tools. Use them every day. But never stop building your own depth.&lt;/p&gt;

&lt;p&gt;Because the day a really hard bug hits production at 2 AM — your client is not going to call ChatGPT.&lt;/p&gt;

&lt;p&gt;They are going to call you.&lt;/p&gt;

&lt;p&gt;Make sure you are ready to answer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;By Mostafijur Rahman — Backend Engineer&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Originally published at &lt;a href="https://mrsajib.com/articles/ai-replace-backend-developers" rel="noopener noreferrer"&gt;mrsajib.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>career</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Securing Your Django Application: Best Practices for Preventing XSS, CSRF, and More</title>
      <dc:creator>Mostafijur Rahman</dc:creator>
      <pubDate>Wed, 25 Sep 2024 16:20:18 +0000</pubDate>
      <link>https://dev.to/mostafij/securing-your-django-application-best-practices-for-preventing-xss-csrf-and-more-27me</link>
      <guid>https://dev.to/mostafij/securing-your-django-application-best-practices-for-preventing-xss-csrf-and-more-27me</guid>
      <description>&lt;p&gt;Security should always be at the forefront of any web development project. With Django, you get a framework that provides a lot of built-in security features, but there are still steps you must take to ensure your application is secure. In this post, we'll explore some best practices for preventing common web vulnerabilities such as Cross-Site Scripting (XSS), SQL Injection, Cross-Site Request Forgery (CSRF), and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. XSS Injection and HTML Injection Prevention
&lt;/h2&gt;

&lt;p&gt;Cross-site scripting (XSS) attacks occur when malicious scripts are injected into websites, typically via user input fields. To prevent XSS, you must sanitize all user input by removing dangerous tags, attributes, and scripts. The &lt;code&gt;bleach&lt;/code&gt; library was traditionally used for sanitizing input, but it has been deprecated since 2023. A great alternative is html-sanitizer, which can effectively sanitize user input.&lt;/p&gt;

&lt;p&gt;In addition to sanitizing input, you should leverage Django's built-in &lt;code&gt;escape&lt;/code&gt; filters within templates. These filters ensure that user-generated content is safely escaped, preventing dangerous HTML from rendering.&lt;/p&gt;

&lt;p&gt;Be cautious when using Django template tags like &lt;code&gt;is_safe&lt;/code&gt;, &lt;code&gt;safe&lt;/code&gt;, &lt;code&gt;mark_safe&lt;/code&gt;, and especially when auto &lt;code&gt;escaping&lt;/code&gt; is turned off. These tools bypass the default escaping mechanisms and should only be used when absolutely necessary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implement a Content Security Policy (CSP)&lt;/strong&gt;&lt;br&gt;
Another layer of protection is to add a Content Security Policy (CSP) to your HTTP headers. A CSP limits which sources are allowed to load content on your site, reducing the risk of XSS attacks. You can implement CSP in Django using middleware like django-csp.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Cross-Site Request Forgery (CSRF) Protection
&lt;/h2&gt;

&lt;p&gt;Django provides built-in protection against Cross-Site Request Forgery (CSRF) attacks. This is achieved by ensuring that any form submissions include a secret, user-specific token. When using HTTPS, this protection is further enhanced by verifying the Referer header for same-origin requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid Disabling CSRF&lt;/strong&gt;&lt;br&gt;
While Django allows you to disable CSRF protection for specific views using the &lt;code&gt;@csrf_exempt&lt;/code&gt; decorator, be very cautious when doing so. Disabling CSRF protection exposes your application to significant risks. Always prioritize the use of HTTPS and consider enforcing HTTP Strict Transport Security (HSTS) for an additional layer of protection.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. SQL Injection Protection
&lt;/h2&gt;

&lt;p&gt;Django is well-equipped to handle SQL injection attacks by using parameterized queries. When you work with Django's ORM, user input is automatically escaped, preventing SQL injection vulnerabilities.&lt;/p&gt;

&lt;p&gt;However, when you manually execute raw SQL queries using methods like &lt;code&gt;RawSQL&lt;/code&gt; or &lt;code&gt;extra()&lt;/code&gt;, you must ensure that user-controlled inputs are properly sanitized and escaped. If you are ever in doubt, it's safer to use Django's ORM methods.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Clickjacking Protection
&lt;/h2&gt;

&lt;p&gt;Clickjacking is a sneaky attack where a malicious actor hijacks legitimate clicks and routes them to hidden, malicious pages. For instance, a user might think they're clicking a button on a legitimate site, but their clicks are being intercepted by an invisible iframe.&lt;/p&gt;

&lt;p&gt;Django provides &lt;strong&gt;X-Frame-Options&lt;/strong&gt; middleware to prevent your site from being embedded within an iframe. This middleware ensures that your site cannot be framed, offering effective protection against clickjacking attacks.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Securing Django Sessions
&lt;/h2&gt;

&lt;p&gt;Django sessions are a way to store user-specific data, such as login status. Misconfigured session settings can lead to session hijacking or session fixation attacks. Here are a few key configurations to secure your sessions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secure Session Cookies:&lt;/strong&gt; Ensure session cookies are only transmitted over HTTPS.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SESSION_COOKIE_SECURE &lt;span class="o"&gt;=&lt;/span&gt; True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTPOnly:&lt;/strong&gt; Prevent JavaScript from accessing session cookies by setting the &lt;code&gt;HttpOnly&lt;/code&gt; flag.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SESSION_COOKIE_HTTPONLY &lt;span class="o"&gt;=&lt;/span&gt; True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Session Expiry:&lt;/strong&gt; Set a session expiry time to minimize risk.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SESSION_COOKIE_AGE &lt;span class="o"&gt;=&lt;/span&gt; 1209600  &lt;span class="c"&gt;# Two weeks in seconds&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  6. Man-in-the-Middle Attacks (SSL/TLS)
&lt;/h2&gt;

&lt;p&gt;To protect your users' data from Man-in-the-Middle (MITM) attacks, it's crucial to use encryption via HTTPS. Django can be configured to automatically redirect all HTTP traffic to HTTPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SECURE_SSL_REDIRECT &lt;span class="o"&gt;=&lt;/span&gt; True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should also enable HTTP Strict Transport Security (HSTS), which ensures that browsers only communicate with your site over HTTPS in the future:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SECURE_HSTS_SECONDS &lt;span class="o"&gt;=&lt;/span&gt; 31536000  &lt;span class="c"&gt;# 1 year&lt;/span&gt;
SECURE_HSTS_INCLUDE_SUBDOMAINS &lt;span class="o"&gt;=&lt;/span&gt; True
SECURE_HSTS_PRELOAD &lt;span class="o"&gt;=&lt;/span&gt; True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Leverage Django's Built-in Security Checklist
&lt;/h2&gt;

&lt;p&gt;Django provides a built-in security checklist that you can run to ensure your project meets security guidelines. This command will scan your settings.py file and provide warnings if you have any misconfigurations that could expose your application to security risks:&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;python&lt;/span&gt; &lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure to review any warnings or errors and adjust your settings accordingly.&lt;/p&gt;

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

&lt;p&gt;Django offers a robust set of tools and best practices for securing your application. By following the guidelines outlined above, you can significantly reduce the risk of attacks such as XSS, CSRF, SQL injection, and more. Remember, security is not a one-time task—it's an ongoing process of monitoring, patching, and improving.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>django</category>
      <category>security</category>
      <category>csrf</category>
    </item>
  </channel>
</rss>
