<?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>Forem: Rohak</title>
    <description>The latest articles on Forem by Rohak (@rohak-dev).</description>
    <link>https://forem.com/rohak-dev</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3907442%2F25870a56-5fa1-4172-8acf-0c7d91fcdf77.jpg</url>
      <title>Forem: Rohak</title>
      <link>https://forem.com/rohak-dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rohak-dev"/>
    <language>en</language>
    <item>
      <title>Your OS: Designed for Many, Used by One</title>
      <dc:creator>Rohak</dc:creator>
      <pubDate>Tue, 05 May 2026 13:13:15 +0000</pubDate>
      <link>https://forem.com/rohak-dev/your-os-designed-for-many-used-by-one-3a9k</link>
      <guid>https://forem.com/rohak-dev/your-os-designed-for-many-used-by-one-3a9k</guid>
      <description>&lt;p&gt;I reinstalled Windows few months ago. Three days later, I was still not done with the Settings, Control Panel, Admin account setup and Software installations.&lt;/p&gt;

&lt;p&gt;Uninstalling unnecessary or unused stuff is the next nightmare. &lt;code&gt;APPDATA&lt;/code&gt; (Local, LocalLow, Roaming), &lt;code&gt;Program Files&lt;/code&gt; (Normal and x86), &lt;code&gt;ProgramData&lt;/code&gt;, registry, random folders buried in &lt;code&gt;Documents&lt;/code&gt;. Don't get me started on the million &lt;code&gt;cache&lt;/code&gt; and &lt;code&gt;temp/tmp&lt;/code&gt; directories.&lt;/p&gt;

&lt;p&gt;Each one is a breadcrumb left behind by an app that assumed it could scatter its files wherever it pleased and you should be fine with it. I wasn't fine with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revo Uninstaller&lt;/strong&gt; and equivalent tools exists entirely because Windows can't clean up after itself. These &lt;em&gt;third-party tools'&lt;/em&gt; entire purpose is to fix a problem that shouldn't exist. And it's still clunky: values (set/unset) mismatch between the app and the system, updating values in the app doesn't change the system settings, etc.&lt;/p&gt;

&lt;p&gt;Linux is better, but not solved. Dotfiles and directories in &lt;code&gt;~/.config&lt;/code&gt;, &lt;code&gt;~/.local&lt;/code&gt;, &lt;code&gt;~/.cache&lt;/code&gt;, package conflicts between system-wide and user-wide installs. There's an ongoing argument, that's been going on for decades, about where things should live.  It hasn't produced a consensus.&lt;/p&gt;

&lt;p&gt;Same problem. Different aesthetics.&lt;/p&gt;




&lt;h2&gt;
  
  
  Brief History on OS Design Choices
&lt;/h2&gt;

&lt;p&gt;This isn't accidental. Blame historical tech-debt.&lt;/p&gt;

&lt;p&gt;Modern OS architecture descends from multi-user mainframe and Unix traditions where many users shared one machine. Separating user data, system data and application data made sense when the alternative was users reading each other's files.&lt;/p&gt;

&lt;p&gt;But that's not how most personal computers are used today. My laptop has one user. Most company devices have one owner. The multi-user security model is still there, largely intact, protecting me from... myself and making sure I don't hand over the keys to the system to a bad actor due to a misclick or not reading installation permissions.&lt;/p&gt;

&lt;p&gt;The architecture hasn't caught up with the use case.&lt;/p&gt;




&lt;h2&gt;
  
  
  My 3-Pointer Design Principles
&lt;/h2&gt;

&lt;p&gt;This isn't an implementation proposal and I'm not writing a kernel. These are design suggestions: a set of principles I'd want an OS to follow, &lt;strong&gt;IF&lt;/strong&gt; I had a say in development. These are OS-agnostic principles, applicable to Windows or Linux equally.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. One User, One Pseudo-Sudo
&lt;/h3&gt;

&lt;p&gt;A personal OS should have exactly two privilege levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The user:&lt;/strong&gt; you, for everything you do day to day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A pseudo-sudo account:&lt;/strong&gt; for installs, system changes and anything that touches the OS itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The critical distinction: the pseudo-sudo account has a separate password from your sign-in password, and it has no home directory, no user data, no persistent session. It exists only to authorise privileged actions. It is not a person, it is &lt;strong&gt;ONLY&lt;/strong&gt; a key.&lt;/p&gt;

&lt;p&gt;No root with a home directory. No administrator account you accidentally leave logged in. No &lt;code&gt;sudo&lt;/code&gt; that remembers your password for five minutes and quietly lets the next command through.&lt;/p&gt;

&lt;p&gt;Two clear levels with a hard boundary between them.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. App-Local Everything
&lt;/h3&gt;

&lt;p&gt;Every installed application should own its own config, cache and data, stored within its own directory. Not in &lt;code&gt;%APPDATA%&lt;/code&gt;. Not in &lt;code&gt;~/.config/appname&lt;/code&gt;. In the app directory itself.&lt;/p&gt;

&lt;p&gt;The benefits are obvious once you say them out loud:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uninstalling an app means deleting its folder. Nothing left behind.&lt;/li&gt;
&lt;li&gt;Moving an app means moving its folder. Config comes with it.&lt;/li&gt;
&lt;li&gt;Auditing what an app stores means looking in one place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default should be: your files live with you, the app's files live with the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. One Global Config SEAN File
&lt;/h3&gt;

&lt;p&gt;System settings: display, network, audio, locale, power, etc., should live in a single, human-readable global config file. One file, I dub as &lt;code&gt;global-conf.sean&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SEAN&lt;/strong&gt; &lt;em&gt;(pronounced as "Shaun", a nod to JSON file type and Heavy Rain, the game)&lt;/em&gt; stands for: &lt;strong&gt;S&lt;/strong&gt;ystem &lt;strong&gt;E&lt;/strong&gt;ntity &lt;strong&gt;A&lt;/strong&gt;ttribute &lt;strong&gt;N&lt;/strong&gt;otation file. Format or nomenclature is less important than the principle though.&lt;/p&gt;

&lt;p&gt;Template &lt;code&gt;global-conf.sean&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;# global-conf.sean
# System Entity Attribute Notation
# Version: 1.0.0

[system]
system-name     = my-machine
architecture    = x86_64

-&amp;gt; [display]
# Display nested inside system and can be called as display.resolution alternatively
resolution      = 2560x1440
refresh_rate    = 144
scaling         = 125%

-&amp;gt; [audio]
# alternate nesting method
output.device   = default
output.volume   = 25
input.device    = default
input.volume    = 100

--&amp;gt; [headphones]
# Headphones settings nestes inside audio, which in turn is, inside system

[power]
sleep.on_battery     = 15min
sleep.on_ac          = 30min
fast_boot            = false

[network]
hostname        = my-machine
dns.primary     = 1.1.1.1
dns.secondary   = 8.8.8.8
ipv6            = true
firewall.mode   = strict

[security]
screen_lock          = true
screen_lock.timeout  = 5min
encrypt_storage      = true
pseudo_sudo.timeout  = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is better than settings scattered across a registry with 200,000 keys, or being split between &lt;code&gt;/etc&lt;/code&gt;, &lt;code&gt;/usr/share&lt;/code&gt;, and a dozen other locations, or requiring a GUI to change a setting that is, fundamentally, just a key-value pair.&lt;/p&gt;

&lt;p&gt;One file. Version-controllable. Readable without a special tool. Transferable to a new machine without hassle.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Benefits
&lt;/h2&gt;

&lt;p&gt;If these three principles were the default, a few things become possible that currently aren't.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reproducible setups:&lt;/strong&gt; If your system config lives in one file and apps are self-contained, your entire OS setup becomes version-controllable. Fresh install means cloning a repo and running a script. Your &lt;code&gt;global-conf.sean&lt;/code&gt; is your IaC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated CI/CD:&lt;/strong&gt; System changes go through a pipeline, not a manual click-through. Roll back a bad config change the same way you'd roll back a bad deploy. This exists for servers. It should exist for personal machines too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-hardened images:&lt;/strong&gt; One can easily build and distribute a baseline image as an add-on. For e.g., a hardened Windows or a Windows-like Linux distro becomes easier to build, maintain and distribute when the architecture supports it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;True portability:&lt;/strong&gt; New machine, same environment. No dotfile managers, backup tools or manual reinstallation. Just the file and the app folders.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Already Available Alternatives
&lt;/h2&gt;

&lt;p&gt;I'm not claiming these ideas are new. I'm claiming they're not the default and they should be, at least for power users, developers or even in a singly owned system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NixOS&lt;/strong&gt; comes closest to my principle 3. Its declarative &lt;code&gt;configuration.nix&lt;/code&gt; is similar to the kind of global config I'm describing. The trade-off is complexity. NixOS has a steep learning curve that makes it inaccessible to most users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flatpak and Snap&lt;/strong&gt; move toward my principle 2. Containerised apps with sandboxed storage. But they layer on top of existing systems rather than replacing the underlying model, and config portability is still limited.
&lt;strong&gt;macOS app bundles&lt;/strong&gt; gets it right for most apps. But &lt;code&gt;~/Library&lt;/code&gt; still exists, still accumulates and still requires manual archaeology to clean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ChromeOS&lt;/strong&gt; implements principle 1 in spirit. It's a single-user OS with a simple privilege model. The trade-off is that it's also a locked-down, cloud-dependent system that trades local control for simplicity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of them implement all three. None of them make these principles the default, out of the box, for a general-purpose OS.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Current Blockers
&lt;/h2&gt;

&lt;p&gt;I've been arguing one side. Here's the other.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared state is a real problem.&lt;/strong&gt; If every app owns its config locally, &lt;em&gt;what happens when two apps need to communicate?&lt;/em&gt; A global config for system settings is clean, but apps interact with each other as much as the system. Some shared state is necessary or maybe a non-conventional way of thinking and implementation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The pseudo-sudo model has edge cases.&lt;/strong&gt; &lt;em&gt;What happens with background services that always need admin access?&lt;/em&gt; Daemons, update managers, antivirus. These don't fit neatly into "user action requiring a one-time key". The model works for interactive installs. It gets complicated for persistent system processes. App/process level elevation can be a thing, but that opens another can of security worms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backwards compatibility.&lt;/strong&gt; Any OS that wanted to implement this would be breaking decades of software assumptions. Apps expect &lt;code&gt;%APPDATA%&lt;/code&gt; to exist. They expect to write to &lt;code&gt;/etc&lt;/code&gt;. &lt;em&gt;Path migrations are hard.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't reasons not to try. They're reasons the design is harder than it looks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I want to reinstall my OS and be productive in the matter of hours, not days.&lt;/li&gt;
&lt;li&gt;I want to uninstall an app and have it actually be gone.&lt;/li&gt;
&lt;li&gt;I want to understand what my system is doing by reading a file, not running a registry editor with seemingly random numbers treated as flag/conditional values/parameters.&lt;/li&gt;
&lt;li&gt;Personal computers are personal. The OS should be designed like it knows that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;PS:&lt;/strong&gt; &lt;em&gt;This is a design opinion piece, not an implementation proposal. If you disagree with the trade-offs or think I've missed something, let's chat. I'd genuinely like to hear more on this.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>osdesign</category>
      <category>architecture</category>
      <category>designsystem</category>
    </item>
    <item>
      <title>Technical Write-up: Comparing CMS Architectures</title>
      <dc:creator>Rohak</dc:creator>
      <pubDate>Fri, 01 May 2026 11:56:08 +0000</pubDate>
      <link>https://forem.com/rohak-dev/technical-write-up-comparing-cms-architectures-bii</link>
      <guid>https://forem.com/rohak-dev/technical-write-up-comparing-cms-architectures-bii</guid>
      <description>&lt;p&gt;I had a question I couldn't answer from documentation alone: &lt;em&gt;how different does it actually feel to build similar systems in Django vs Express.js?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Similar features and scope, but two different stacks: built in parallel so the comparison would be honest. This post is the &lt;em&gt;pseudo-technical&lt;/em&gt; summary of what I observed.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Project
&lt;/h2&gt;

&lt;p&gt;A Blog/CMS with standard requirements: user authentication, post creation and management, media uploads, role-based access (User and Admin) and a templated frontend (nothing exotic). The kind of system where framework and architecture matter more than LAF &lt;em&gt;(Look and Feel)&lt;/em&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Express.js&lt;/th&gt;
&lt;th&gt;Django&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Node.js (JavaScript)&lt;/td&gt;
&lt;td&gt;Python 3.13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MongoDB via Mongoose&lt;/td&gt;
&lt;td&gt;SQLite via Django ORM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authentication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Manual JWT + cookie-parser&lt;/td&gt;
&lt;td&gt;Django Built-in Auth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Media Handling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;multer (server-side storage)&lt;/td&gt;
&lt;td&gt;FileField / ImageField&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Templating&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;EJS + Bootstrap 5&lt;/td&gt;
&lt;td&gt;Django Templates + Bootstrap 5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both implementations are in separate repositories. The goal was &lt;strong&gt;functional parity&lt;/strong&gt;, not just similar features, but equivalent behaviour so the comparison would be meaningful and as fair and impartial as possible.&lt;br&gt;&lt;br&gt;
&lt;em&gt;Both repositories are linked in the &lt;a href="https://github.com/Rohak-Git-Gud/research-cms-archs-express-django" rel="noopener noreferrer"&gt;README&lt;/a&gt; file if you want to look at the full implementations.&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Authentication: Visible Contrast
&lt;/h2&gt;

&lt;p&gt;This is where the difference between the two frameworks is most visible. Not in logic flow but its implementation.&lt;/p&gt;
&lt;h3&gt;
  
  
  Express.js: You Own Everything
&lt;/h3&gt;

&lt;p&gt;In Express, authentication doesn't exist until you build it. Here's the user model handling password hashing and login validation:&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;// Pre-save: salt generation and hashing&lt;/span&gt;
&lt;span class="nx"&gt;userSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;save&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isModified&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&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;next&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;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;passwordHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;salt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&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;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;salt&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;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;passwordHash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Login: validate credentials and return token&lt;/span&gt;
&lt;span class="nx"&gt;userSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userValidatorandTokenise&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="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&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;email&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;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User Not Found&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;hashChk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;hashChk&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Incorrect Password&lt;/span&gt;&lt;span class="dl"&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;createUserToken&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is before touching JWT issuance, cookie management or route protection middleware, all of which live in separate files that you have to write as well.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The upside:&lt;/strong&gt; you understand exactly what's happening at every step.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The downside:&lt;/strong&gt; every step is a surface area for mistakes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Django: The Framework Handles It
&lt;/h3&gt;

&lt;p&gt;The Django equivalent for user profiles is this:&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;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;user&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;OneToOneField&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;on_delete&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="n"&gt;CASCADE&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;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default.jpg&lt;/span&gt;&lt;span class="sh"&gt;"&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;profile_pics&lt;/span&gt;&lt;span class="sh"&gt;"&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;__str__&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="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="si"&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;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s Profile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&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;image&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;op_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op_size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;img&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Profile&lt;/code&gt; model handles the custom parts: profile photo with auto-resizing.&lt;br&gt;&lt;br&gt;
Password hashing, session management, login/logout views, CSRF protection, the admin interface, all of that comes from &lt;code&gt;django.contrib.auth&lt;/code&gt;, which you import in one line.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The upside:&lt;/strong&gt; The entire custom auth logic is roughly zero lines in Django.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The downside:&lt;/strong&gt; Sometimes it may feel rigid to the 'tinkerers'.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Data Modelling: Flexibility vs. Integrity
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;MongoDB with Mongoose&lt;/strong&gt; gave you schema flexibility and document structure could evolve without migrations. For a project where post metadata might change frequently, that's an advantage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Django's ORM&lt;/strong&gt; enforced relational integrity from the start. Every schema change goes through a migration. That may seem like a time-loss during development, but it is this friction that prevents bugs where your data structure and application logic drift apart.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Neither is universally better&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The answer: if your &lt;em&gt;data structure is uncertain&lt;/em&gt;, MongoDB's flexibility is worth having.&lt;/li&gt;
&lt;li&gt;If your &lt;em&gt;data structure is known upfront&lt;/em&gt;, Django's migrations give you a paper trail and consistency guarantees that are hard to replicate manually.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Developer Velocity: Whatcha Ya Building
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Django&lt;/strong&gt; won on features that map directly to what the framework provides out-of-the-box. The admin interface appears in about 10 lines of configuration. User management, permissions, CRUD scaffolding, placeholder/fake data, all faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Express.js&lt;/strong&gt; won on understanding. Building the middleware pipeline manually meant you could debug it precisely, extend it without fighting the framework and understand exactly what each request was doing at every point in its lifecycle.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The pattern I noticed:&lt;/strong&gt; Django is faster when the framework has already solved your problem. Express is faster when it hasn't, because you're not working around someone else's abstractions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security Posture
&lt;/h2&gt;

&lt;p&gt;This is where I'd push back against the framing that custom implementations are inherently more flexible or powerful.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Django&lt;/strong&gt;'s session management, CSRF protection and password hashing are production-hardened defaults. They're not configurable because the correct configuration is already in place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Express&lt;/strong&gt; implementation requires you to make explicit decisions about salt length, hash algorithm, cookie configuration and token expiry. I feel like I made reasonable choices, but they were &lt;strong&gt;my&lt;/strong&gt; choices, made at implementation time and they're only as good as &lt;strong&gt;my knowledge at that moment&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For security-sensitive logic specifically, "batteries included" maybe is better if you're not deep in security engineering. If you know what you're doing and aren't bothered by time constraints, you can try custom auth.&lt;/p&gt;




&lt;h2&gt;
  
  
  Takeaway: Stop thinking about food
&lt;/h2&gt;

&lt;p&gt;Building both taught me something the documentation doesn't say directly: the choice between Express.js vs Django isn't just about JavaScript vs Python. It's about how much of the stack you want to own. Apart from that, if you're a working professional, you also have to take into consideration, the leadership and the existing tech that might give you nightmares during integration testing if you chose incompatible stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Express&lt;/strong&gt; is like an empty house with just walls where you can design your interior while &lt;strong&gt;Django&lt;/strong&gt; is like moving into a fully furnished house.&lt;/p&gt;

&lt;p&gt;On the reference projects, both implementations were built with reference to these two tutorial series:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PL-osiE80TeTtoQCKZ03TU5fNfx2UY6U4p" rel="noopener noreferrer"&gt;Corey Schafer's Django series&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLinedj3B30sDby4Al-i13hQJGQoRQDfPo" rel="noopener noreferrer"&gt;Piyush Garg's Node + Express series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The implementations follow both references closely with minor tweaks. Comments functionality was intentionally excluded from both to keep the scope focused on the architectural comparison rather than feature completeness.&lt;/p&gt;

&lt;p&gt;What's worth noting: the tutorials reflect their frameworks' philosophies as much as the frameworks themselves do. Corey's series is methodical and production-minded, it teaches you Django the way Django wants to be used. Piyush's series is fast and mechanical, it prioritises understanding what's actually happening under the hood over getting to a finished product quickly. The code difference and the teaching difference point in the same direction.&lt;/p&gt;




</description>
      <category>architecture</category>
      <category>backenddevelopment</category>
      <category>django</category>
      <category>express</category>
    </item>
  </channel>
</rss>
