<?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: Abdelhakim mohamed </title>
    <description>The latest articles on DEV Community by Abdelhakim mohamed  (@hakimmohamed).</description>
    <link>https://dev.to/hakimmohamed</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%2F1451389%2F3b4d0848-78f8-46af-94c5-43a112bc2d0c.jpg</url>
      <title>DEV Community: Abdelhakim mohamed </title>
      <link>https://dev.to/hakimmohamed</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hakimmohamed"/>
    <language>en</language>
    <item>
      <title>Why I Built My Own IAP System Instead of Using RevenueCat</title>
      <dc:creator>Abdelhakim mohamed </dc:creator>
      <pubDate>Sat, 01 Aug 2026 16:06:39 +0000</pubDate>
      <link>https://dev.to/hakimmohamed/why-i-built-my-own-iap-system-instead-of-using-revenuecat-2372</link>
      <guid>https://dev.to/hakimmohamed/why-i-built-my-own-iap-system-instead-of-using-revenuecat-2372</guid>
      <description>&lt;p&gt;Apple takes 15%. RevenueCat takes another 1% of tracked revenue on top.&lt;/p&gt;

&lt;p&gt;That's one dollar in six, and the last slice goes to a service that forwards JSON between my server and Apple's.&lt;/p&gt;

&lt;p&gt;So I built it myself for &lt;a href="https://www.nutix-app.com/en" rel="noopener noreferrer"&gt;Nutix&lt;/a&gt; over a weekend, both platforms. Claude Code one-shot most of the implementation — and that's the point. The code isn't the hard part anymore. The edge cases are, and they're what took the rest of the weekend.&lt;/p&gt;

&lt;p&gt;This post is those edge cases.&lt;/p&gt;

&lt;p&gt;For react-native-iap I found this post most useful for me &lt;a href="https://www.openiap.dev/docs/setup/react-native" rel="noopener noreferrer"&gt;React Native IAP&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use RevenueCat if&lt;/strong&gt; you're pre-revenue, or you don't want payments to be something you're on call for. At low revenue the 1% is rounding error and you get a real dashboard for free.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build it yourself if&lt;/strong&gt; the fee is a real line item, or you want payments to be something you understand rather than something you configure.&lt;/p&gt;

&lt;p&gt;The usual argument for RevenueCat is cross-platform — one entitlement model across StoreKit and Google Play. I did both myself. It's genuinely more work, but it's a weekend of work, not a quarter.&lt;/p&gt;

&lt;p&gt;Everything below is the Apple side, for simplicity.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The client decides nothing
&lt;/h2&gt;

&lt;p&gt;StoreKit 2 hands you a JWS. The app forwards it and waits. It never decodes it, never checks &lt;code&gt;expiresDate&lt;/code&gt;, never unlocks anything locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;RNIap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;purchaseUpdatedListener&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;purchase&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="c1"&gt;// StoreKit replays old transactions on every launch. Not user intent.&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;pendingPurchaseRef&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;pendingRedemptionRef&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;RNIap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finishTransaction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isConsumable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;subscriptionService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyReceipt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;receiptData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;purchaseToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Finish AFTER the server has it. Non-fatal if it throws.&lt;/span&gt;
  &lt;span class="k"&gt;try&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;RNIap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finishTransaction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;isConsumable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="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="nf"&gt;logIAPError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;finishTransactionFailed&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Finish after your server records the purchase.&lt;/strong&gt; Do it before, and a crash between the two leaves a paying user with nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guard against replays.&lt;/strong&gt; Without that check, my success screen fired on app launch for people who bought weeks ago.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't await &lt;code&gt;requestPurchase&lt;/code&gt;.&lt;/strong&gt; It doesn't resolve when the user dismisses the sheet. Resolve from the listener pair instead. If you're awaiting it today, you have a hung spinner in production.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Ask Apple, don't trust the receipt
&lt;/h2&gt;

&lt;p&gt;Decode the token for &lt;code&gt;originalTransactionId&lt;/code&gt; and environment. Then call the App Store Server API for the real state.&lt;/p&gt;

&lt;p&gt;Gotchas: your auth JWT can't live longer than 60 minutes, and sandbox is on a different host — which you only know after decoding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where mine is wrong:&lt;/strong&gt; Apple's payloads carry a certificate chain you're meant to verify up to their root CA. I don't. I decode and lean on TLS. For the API path that's defensible. For the webhook path it isn't — anything that can POST to my endpoint gets parsed. Use &lt;a href="https://github.com/apple/app-store-server-library-node" rel="noopener noreferrer"&gt;&lt;code&gt;app-store-server-library-node&lt;/code&gt;&lt;/a&gt; and skip my mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Key on &lt;code&gt;originalTransactionId&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;It's Apple's stable ID for the whole subscription lifetime — it survives renewals, plan changes, resubscribes. &lt;code&gt;transactionId&lt;/code&gt; changes every renewal and is useless as a key.&lt;/p&gt;

&lt;p&gt;Your table is a cache. Apple is the truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Most of the work happens while the app is closed
&lt;/h2&gt;

&lt;p&gt;Renewals at 3am. Billing retries. Cancellations from Settings. Refunds. None of it produces a client call — you need App Store Server Notifications V2.&lt;/p&gt;

&lt;p&gt;Two subtypes cost money if you get them wrong:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DID_CHANGE_RENEWAL_STATUS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;// Cancelling a PAID period doesn't end access. Apple sends EXPIRED later.&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;subtype&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AUTO_RENEW_DISABLED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isTrialPeriod&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cancelled&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;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DID_FAIL_TO_RENEW&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;// GRACE_PERIOD means keep them premium.&lt;/span&gt;
  &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subtype&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GRACE_PERIOD&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;grace_period&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;billing_retry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Someone who cancels on day 3 of a paid month keeps premium until day 30. Revoke early and you've stolen 27 days.&lt;/p&gt;

&lt;p&gt;Handlers must be idempotent, and return 200 fast — Apple retries on anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Signing promotional offers
&lt;/h2&gt;

&lt;p&gt;The one section Apple documents completely and uselessly, and the one place the generated code was confidently wrong. Get it wrong and StoreKit fails with a generic error and no hint which field was bad.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SEP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s1"&gt;u2063&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// INVISIBLE SEPARATOR. Write the escape, never the character.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="nx"&gt;bundleId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;               &lt;span class="c1"&gt;// In-App Purchase key, NOT the Server API key&lt;/span&gt;
  &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;offerIdentifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;applicationUsername&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// '' if unused — the empty slot still counts&lt;/span&gt;
  &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;  &lt;span class="c1"&gt;// MILLISECONDS&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="nx"&gt;SEP&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;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SHA256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createPrivateKey&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pem&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;dsaEncoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ieee-p1363&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Node defaults to DER. Apple wants raw r||s.&lt;/span&gt;
  &lt;span class="p"&gt;})&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&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;All four comments are things that failed silently on me. The separator is the worst — it's zero-width, so it won't survive a copy-paste out of rendered docs. That's why half the posts about this are wrong, and probably why the models trained on them are too.&lt;/p&gt;

&lt;p&gt;Introductory offers need none of this. Apple handles eligibility; you just read &lt;code&gt;offerType: 1&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What broke
&lt;/h2&gt;

&lt;p&gt;Sandbox testing is miserable. A month renews in five minutes, accounts get stuck in states you can't clear, and notifications sometimes just don't arrive — so you can't tell a broken handler from a missing event. Budget as much time for testing as for the code.&lt;/p&gt;

&lt;p&gt;That's the real shape of this project now. Generating the implementation is an afternoon. Knowing which of the generated lines will quietly cost you money is the weekend.&lt;/p&gt;

&lt;p&gt;The 1% was the excuse. Wanting to know what happens between a tap and a row in my database was the reason.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ios</category>
      <category>storekit</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>How I Built a Multilingual Food Database Using a Local LLM on an AMD GPU</title>
      <dc:creator>Abdelhakim mohamed </dc:creator>
      <pubDate>Thu, 22 Jan 2026 23:14:09 +0000</pubDate>
      <link>https://dev.to/hakimmohamed/how-i-built-a-multilingual-food-database-using-a-local-llm-on-an-amd-gpu-1f0k</link>
      <guid>https://dev.to/hakimmohamed/how-i-built-a-multilingual-food-database-using-a-local-llm-on-an-amd-gpu-1f0k</guid>
      <description>&lt;p&gt;Two weeks ago, I launched my first app &lt;a href="https://apps.apple.com/us/app/calorie-counter-nutix-ai/id6754726109" rel="noopener noreferrer"&gt;Nutix Ai&lt;/a&gt;, a calorie tracking app focused on simplicity. It's now at 200 downloads, which feels great for a first launch.&lt;/p&gt;

&lt;p&gt;Today I want to share one specific part of the journey: building a food database that supports multiple languages (English, Arabic, Spanish) — not just English like most nutrition apps.&lt;/p&gt;

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

&lt;p&gt;Most calorie tracking apps have English-only databases, or their translations feel like afterthoughts. I wanted native speakers to actually be able to search for foods in their language from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;Simple setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local LLM running on my &lt;strong&gt;AMD 9070XT&lt;/strong&gt; (yes, not NVIDIA — and it worked great)&lt;/li&gt;
&lt;li&gt;Base food data in English&lt;/li&gt;
&lt;li&gt;LLM translates each entry to Arabic and Spanish&lt;/li&gt;
&lt;li&gt;Output stored in my database&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Model Hunt (The Painful Part)
&lt;/h2&gt;

&lt;p&gt;Finding the right model took longer than I expected.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Translation Quality (especially Arabic)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek 7B&lt;/td&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;td&gt;Terrible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek 8B&lt;/td&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;td&gt;Terrible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek 14B&lt;/td&gt;
&lt;td&gt;Very slow&lt;/td&gt;
&lt;td&gt;Still bad&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Llama variants&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Poor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QWEN&lt;/td&gt;
&lt;td&gt;Had high hopes&lt;/td&gt;
&lt;td&gt;Performed worse than expected&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gemma3 7B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~200 tok/s&lt;/td&gt;
&lt;td&gt;Good (1-5% error rate)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gemma3 12B&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~80 tok/s&lt;/td&gt;
&lt;td&gt;Great&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Gemma3 was the winner. The 7B was fast but had occasional errors. The 12B took about 2 seconds per food item but was much more reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;~6,000 foods translated into 3 languages in 4 hours, all generated locally on consumer hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known Issues &amp;amp; What's Next
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Database size&lt;/strong&gt; — 6K foods is a start, but I want to expand to 12+ languages and more regional foods&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search&lt;/strong&gt; — The food search still needs work. Fuzzy matching across multiple languages is tricky&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Looking for Suggestions
&lt;/h2&gt;

&lt;p&gt;If you have ideas for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good sources of food/nutrition data&lt;/li&gt;
&lt;li&gt;Improving multilingual search&lt;/li&gt;
&lt;li&gt;Other languages I should prioritize&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drop them in the comments — I'd genuinely appreciate it.&lt;/p&gt;




&lt;p&gt;You can check out the app here: &lt;a href="https://apps.apple.com/us/app/calorie-counter-nutix-ai/id6754726109" rel="noopener noreferrer"&gt;Nutix on the App Store&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>database</category>
      <category>llm</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Built a nutrition app with React Native + Expo SDK 54 that actually feels native on iOS 👀</title>
      <dc:creator>Abdelhakim mohamed </dc:creator>
      <pubDate>Thu, 04 Dec 2025 01:34:03 +0000</pubDate>
      <link>https://dev.to/hakimmohamed/built-a-nutrition-app-with-react-native-expo-sdk-54-that-actually-feels-native-on-ios-g7c</link>
      <guid>https://dev.to/hakimmohamed/built-a-nutrition-app-with-react-native-expo-sdk-54-that-actually-feels-native-on-ios-g7c</guid>
      <description>&lt;p&gt;Opening beta to the first 1000 users for free&lt;br&gt;
Would love your feedback — what feels off? What could be better?&lt;br&gt;
&lt;a href="https://testflight.apple.com/join/ZVUxTEAt" rel="noopener noreferrer"&gt;https://testflight.apple.com/join/ZVUxTEAt&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>reactnative</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>DDoS Protection: How to Monitor, Detect, and Defend Against Attacks</title>
      <dc:creator>Abdelhakim mohamed </dc:creator>
      <pubDate>Sat, 07 Jun 2025 01:09:13 +0000</pubDate>
      <link>https://dev.to/hakimmohamed/ddos-protection-how-to-monitor-detect-and-defend-against-attacks-5aji</link>
      <guid>https://dev.to/hakimmohamed/ddos-protection-how-to-monitor-detect-and-defend-against-attacks-5aji</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;The Midnight Server Meltdown: What To Do When Your Backend Goes Silent&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine this: it’s a peaceful night, maybe you’re winding down, enjoying a movie, or catching up on some sleep. Suddenly, your phone rings it's your client or your boss. Their voice is urgent:&lt;/p&gt;

&lt;p&gt;“The server is down. Customers can’t access anything. Everything’s super slow!”&lt;/p&gt;

&lt;p&gt;Your heart races. You scramble to your desk, half awake but fully alert now. You SSH into the server or open your monitoring dashboard nothing seems obviously broken. You check the logs they’re there, but not helpful. You hit a few endpoints they respond… eventually. What used to take 200ms is now crawling at 10 seconds or, even worse, timing out.&lt;/p&gt;

&lt;p&gt;At this point, panic is knocking on your door.&lt;/p&gt;

&lt;p&gt;You start digging deeper. CPU usage is spiking, memory is being eaten up, and your server is getting hammered with requests. But they aren’t normal requests they’re overwhelming, coming from a swarm of IPs. That’s when it hits you:&lt;/p&gt;

&lt;p&gt;You're under a DDoS attack. Someone, somewhere, decided to bring your server to its knees.&lt;/p&gt;

&lt;p&gt;Now the real question is what do you do to prevent this from happening again?&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%2Fxfugalkaicbgzoycbqfv.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%2Fxfugalkaicbgzoycbqfv.gif" alt="Image description" width="540" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;&lt;em&gt;Set Up Rate Limiting&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Limit how many requests each IP can make per second using Nginx. This helps slow down bots and flood attempts before they hurt your server.&lt;/p&gt;

&lt;p&gt;If your using Nginx consider adding this inside &lt;code&gt;nginx.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http {
  limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;

  server {
    listen 80;

    location /api/ {
      limit_req zone=mylimit burst=20 nodelay;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;10r/s&lt;/strong&gt; Limits each IP address to a maximum of 10 requests per second.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;zone=mylimit:10m&lt;/strong&gt; Creates a shared memory zone named &lt;code&gt;mylimit&lt;/code&gt; that can track up to approximately 160,000 unique IP addresses with 10MB of memory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;burst=20&lt;/strong&gt; Allows short traffic spikes by permitting up to 20 excess requests to queue temporarily before triggering rate limiting.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;&lt;em&gt;Monitor Abusive IPs&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Keep an eye on which IPs are getting rate-limited. If the same ones show up repeatedly, it’s a red flag something is trying to break in or overwhelm your system.&lt;/p&gt;

&lt;p&gt;Add the following to your &lt;code&gt;nginx.conf&lt;/code&gt; to enable logging:&lt;br&gt;
This ensures that NGINX logs blocked or rate-limited requests along with the client’s IP address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http {
  limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
  log_format rate_limited '$remote_addr - $request - $status - $http_user_agent';

  map $status $log_rate_limit {
    503     1;
    default 0;
  }

  access_log /var/log/nginx/ratelimited.log rate_limited if=$log_rate_limit;

  server {
    listen 80;

    location /api/ {
      limit_req zone=mylimit burst=20 nodelay;
    }
  }
}

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

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;&lt;em&gt;Automatically Ban Suspicious IPs&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Use tools like fail2ban to detect patterns of abuse and block the source IP for a set period. This adds another layer of defense beyond just rate limiting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update
sudo apt install fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/etc/fail2ban/jail.conf&lt;/code&gt;: Default config&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/etc/fail2ban/jail.d/&lt;/code&gt;: Your custom jail files&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/etc/fail2ban/filter.d/&lt;/code&gt;: Contains filters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s say we want to block any IP that gets rate-limited too often (&lt;code&gt;503&lt;/code&gt; status in your Nginx log).&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a custom jail
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/etc/fail2ban/jail.d/nginx-limit.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[nginx-limit]&lt;/span&gt;
&lt;span class="py"&gt;enabled&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;http,https&lt;/span&gt;
&lt;span class="py"&gt;filter&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;nginx-limit&lt;/span&gt;
&lt;span class="py"&gt;logpath&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/var/log/nginx/ratelimited.log&lt;/span&gt;
&lt;span class="py"&gt;maxretry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10&lt;/span&gt;
&lt;span class="py"&gt;findtime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;60&lt;/span&gt;
&lt;span class="py"&gt;bantime&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;600&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Ban IPs that match the filter 10 times in 60 seconds&lt;/li&gt;
&lt;li&gt;Ban them for 10 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Create the filter file&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/etc/fail2ban/filter.d/nginx-limit.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Definition]&lt;/span&gt;
&lt;span class="py"&gt;failregex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;^&amp;lt;HOST&amp;gt; - .+ - 503 - .*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This regex matches lines like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;192.168.1.5 - /api/endpoint - 503 - curl/7.58.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It extracts the &lt;code&gt;&amp;lt;HOST&amp;gt;&lt;/code&gt; (IP address) and counts the hits.&lt;/p&gt;

&lt;p&gt;Start or Restart Fail2Ban&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check Status and Banned IPs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;fail2ban-client status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check specific jail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;fail2ban-client status nginx-limit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unban a specific IP (if needed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;fail2ban-client &lt;span class="nb"&gt;set &lt;/span&gt;nginx-limit unbanip 192.168.1.5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Use reCAPTCHA to Stop Bots Cold&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Especially for sensitive actions like login or guest access, adding Google reCAPTCHA can stop automated scripts in their tracks, without bothering real users much.&lt;/p&gt;

&lt;p&gt;Thanks for reading my article 🙌&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%2Fywk9tph8tmm6trwldt0v.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%2Fywk9tph8tmm6trwldt0v.gif" alt="Image description" width="350" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Understanding Express Routes, Controllers, and Services</title>
      <dc:creator>Abdelhakim mohamed </dc:creator>
      <pubDate>Sat, 28 Sep 2024 21:51:42 +0000</pubDate>
      <link>https://dev.to/hakimmohamed/understanding-express-routes-controllers-and-services-a-beginners-guide-nodejs-tutorial-part-12-2cd4</link>
      <guid>https://dev.to/hakimmohamed/understanding-express-routes-controllers-and-services-a-beginners-guide-nodejs-tutorial-part-12-2cd4</guid>
      <description>&lt;h4&gt;
  
  
  Introduction
&lt;/h4&gt;

&lt;p&gt;If you're new to Express.js or Node.js, you might have heard about "routes," "controllers," and "services." At first, these terms sound technical, but trust me understanding them can really transform how you build your apps. Not only will your code look cleaner, but it'll also make your life easier as your app grows. In this guide, we'll break down what each of these components does and why separating them is a game-changer for any Express project.&lt;/p&gt;

&lt;p&gt;Let's start by setting up a basic Express app and organizing everything like a pro!&lt;/p&gt;

&lt;h4&gt;
  
  
  Setting Up Your Project
&lt;/h4&gt;

&lt;p&gt;First things first let’s create a new Node.js project. Fire up your terminal and follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initialize your project&lt;/strong&gt; by running:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install Express&lt;/strong&gt; to get the magic going:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   npm &lt;span class="nb"&gt;install &lt;/span&gt;express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Now, let’s structure our project to keep things clean. Here’s how your folder setup should look:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-express-app
│
├── src
│   ├── controllers
│   ├── routes
│   ├── services
│
├── app.js
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fcmbce10i2adtwdwe7m65.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcmbce10i2adtwdwe7m65.png" alt="Image description" width="440" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We'll walk through what goes into each of these folders. Don't worry if this looks like a lot; it's easier than it seems!.&lt;/p&gt;




&lt;h4&gt;
  
  
  Routes: The Traffic Cops of Your App
&lt;/h4&gt;

&lt;p&gt;Think of routes as traffic cops—they decide where each request should go. They don’t do much heavy lifting themselves but delegate the real work to controllers.&lt;/p&gt;

&lt;p&gt;Here’s how you can create a basic route for user-related requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/routes/userRoutes.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Router&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;userController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../controllers/userController&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Define routes&lt;/span&gt;
&lt;span class="nx"&gt;router&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAllUsers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;router&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;/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, when someone hits &lt;code&gt;/users&lt;/code&gt; with a GET request, our app calls the &lt;code&gt;getAllUsers&lt;/code&gt; function from the controller. Pretty neat, right?&lt;/p&gt;




&lt;h4&gt;
  
  
  Controllers: The Brains of the Operation
&lt;/h4&gt;

&lt;p&gt;Now, controllers are where the logic happens. They take what the routes send their way and handle all the complex stuff like fetching data or saving a new user.&lt;/p&gt;

&lt;p&gt;Let’s build a simple controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/controllers/userController.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../services/userService&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAllUsers&lt;/span&gt; &lt;span class="o"&gt;=&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;users&lt;/span&gt; &lt;span class="o"&gt;=&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;fetchUsers&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="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&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;users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createUser&lt;/span&gt; &lt;span class="o"&gt;=&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;newUser&lt;/span&gt; &lt;span class="o"&gt;=&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;addUser&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="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;newUser&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;Here, the controller is getting data from a service (which we'll talk about next) and sending a response back. Think of the controller as the middleman between routes and services—it doesn't handle data directly but knows who to ask.&lt;/p&gt;




&lt;h4&gt;
  
  
  Services: The Real Workers
&lt;/h4&gt;

&lt;p&gt;Services handle the real business logic, like interacting with a database or managing data. This keeps your controllers nice and clean.&lt;/p&gt;

&lt;p&gt;Check out this basic service that manages a list of users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/services/userService.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetchUsers&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;return&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&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;user&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;Here, we’re storing users in an array (for simplicity). Services help keep your controllers lightweight and focused on handling requests instead of worrying about the data itself.&lt;/p&gt;




&lt;h4&gt;
  
  
  Bringing It All Together in &lt;code&gt;app.js&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Once you have your routes, controllers, and services set up, you need to wire everything together in your main application file (&lt;code&gt;app.js&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="c1"&gt;// app.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&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;userRoutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/routes/userRoutes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userRoutes&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;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&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="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="s2"&gt;`Server running on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this file, we tell Express to use our &lt;code&gt;userRoutes&lt;/code&gt; for any requests that start with &lt;code&gt;/api&lt;/code&gt;. This makes it easy to organize all your routes under one common base path.&lt;/p&gt;




&lt;h4&gt;
  
  
  How Your Final Directory Should Look
&lt;/h4&gt;

&lt;p&gt;Once you've set up everything, your directory structure should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-express-app
│
├── src
│   ├── controllers
│   │   └── userController.js
│   ├── routes
│   │   └── userRoutes.js
│   ├── services
│   │   └── userService.js
│
├── app.js
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  Wrapping Up
&lt;/h4&gt;

&lt;p&gt;By splitting your app into routes, controllers, and services, you've just taken the first step towards writing better, cleaner code! Not only does this make your project easier to manage now, but it also saves you headaches as your app gets more complex. Plus, your future self (and your teammates) will thank you!&lt;/p&gt;

&lt;p&gt;Happy Coding😊&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
