<?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: Nicolás Andrés Cano Leal</title>
    <description>The latest articles on DEV Community by Nicolás Andrés Cano Leal (@nicolasandrescl).</description>
    <link>https://dev.to/nicolasandrescl</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%2F3277529%2F17913cbd-8300-4778-ac07-89102a7cf35e.jpg</url>
      <title>DEV Community: Nicolás Andrés Cano Leal</title>
      <link>https://dev.to/nicolasandrescl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nicolasandrescl"/>
    <language>en</language>
    <item>
      <title>Portfolio v2.0: three themes, one headline, and every number checked against the repo</title>
      <dc:creator>Nicolás Andrés Cano Leal</dc:creator>
      <pubDate>Sun, 18 Oct 2026 04:48:22 +0000</pubDate>
      <link>https://dev.to/nicolasandrescl/portfolio-v20-three-themes-one-headline-and-every-number-checked-against-the-repo-47fd</link>
      <guid>https://dev.to/nicolasandrescl/portfolio-v20-three-themes-one-headline-and-every-number-checked-against-the-repo-47fd</guid>
      <description>&lt;p&gt;My portfolio just hit &lt;strong&gt;v2.0&lt;/strong&gt;. It's a Django 5.2 + DRF API with a React 19 build embedded and served by Django, PostgreSQL on Neon, deployed on PythonAnywhere.&lt;/p&gt;

&lt;p&gt;The visible change is a &lt;strong&gt;theme switcher&lt;/strong&gt; in the top-right corner with three palettes: &lt;strong&gt;Cian&lt;/strong&gt; (the default, a midnight background with a cyan-to-violet aurora), &lt;strong&gt;Violeta&lt;/strong&gt; (light) and &lt;strong&gt;Ámbar&lt;/strong&gt; (warm and sober). The less visible change taught me more: making sure my portfolio, my CV and my LinkedIn tell the &lt;strong&gt;same story with the same numbers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 Live: &lt;a href="https://nicolasandrescl.pythonanywhere.com" rel="noopener noreferrer"&gt;nicolasandrescl.pythonanywhere.com&lt;/a&gt; · &lt;a href="https://github.com/NicolasAndresCL/MiPortafolioDjango" rel="noopener noreferrer"&gt;backend&lt;/a&gt; · &lt;a href="https://github.com/NicolasAndresCL/Portafolio-Frontend-React" rel="noopener noreferrer"&gt;frontend&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1 — A theme system that never re-renders React
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Themes as plain data
&lt;/h3&gt;

&lt;p&gt;Every component was already styled with Stitches tokens (&lt;code&gt;$accent&lt;/code&gt;, &lt;code&gt;$surface&lt;/code&gt;, &lt;code&gt;$syntaxFunction&lt;/code&gt;…). So I didn't need a ThemeProvider. A theme is just &lt;strong&gt;a set of token values&lt;/strong&gt;, written as plain data with no Stitches import:&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/themes/palettes.js (trimmed)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;THEMES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cian&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cian&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#070b14&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#22d3ee&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;onAccent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#04111a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;heading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'Space Grotesk', 'Segoe UI', sans-serif&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&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;// violeta, ambar …&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeping it free of Stitches means I can test it without a styling runtime. This test catches the most common theming bug, a theme that quietly inherits a color from another one because it forgot to define a token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;every theme defines the same tokens&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="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;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DEFAULT_THEME&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;colorKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;THEMES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;colorKeys&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  One class on &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Each palette becomes a Stitches &lt;code&gt;createTheme&lt;/code&gt; class, and switching themes just swaps a class on &lt;code&gt;&amp;lt;html&amp;gt;&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;themes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;THEMES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&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;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;createTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;themeClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fonts&lt;/span&gt; &lt;span class="p"&gt;})]),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;themes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ← more on this line below&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&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;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;THEMES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;themeClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
  &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;themeClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colorScheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dark&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&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;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// …and updates &amp;lt;meta name="theme-color"&amp;gt; for the mobile browser bar&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; and not a wrapper &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;? Radix renders dropdowns in a &lt;strong&gt;Portal&lt;/strong&gt; attached to &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. If the theme lived on a div inside the app, every menu would open with the wrong colors.&lt;/p&gt;

&lt;p&gt;The switcher itself is a Radix &lt;code&gt;DropdownMenu&lt;/code&gt; with a &lt;code&gt;RadioGroup&lt;/code&gt;. That gives &lt;code&gt;menuitemradio&lt;/code&gt; roles, keyboard navigation, Escape to close and focus management for free. The choice is stored in &lt;code&gt;localStorage&lt;/code&gt;, wrapped in &lt;code&gt;try/catch&lt;/code&gt;: private mode or blocked storage falls back to Cian instead of breaking the site.&lt;/p&gt;

&lt;h3&gt;
  
  
  No flash on reload
&lt;/h3&gt;

&lt;p&gt;If you apply the saved theme in a &lt;code&gt;useEffect&lt;/code&gt;, every reload paints the default theme first and then switches. The fix is a tiny inline script that runs &lt;strong&gt;before&lt;/strong&gt; React loads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#070b14&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"violeta"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#fbf8ff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;color-scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"ambar"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#130d0a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;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="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt; &lt;span class="o"&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;cian&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;violeta&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;ambar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cian&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;portafolio-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;key&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="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cian&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;theme-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;})();&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I verified it with Playwright: save Ámbar, reload, screenshot immediately. Even the loading spinner was already amber.&lt;/p&gt;




&lt;h2&gt;
  
  
  Four traps I stepped on
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Stitches injects themes lazily.&lt;/strong&gt; A &lt;code&gt;createTheme&lt;/code&gt; rule only reaches the stylesheet when something reads &lt;code&gt;theme.className&lt;/code&gt;. My inline script added &lt;code&gt;theme-ambar&lt;/code&gt; to &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; before any component had touched that theme, so the class existed but &lt;strong&gt;styled nothing&lt;/strong&gt;. That's the reason for the "useless-looking" line above: reading &lt;code&gt;.className&lt;/code&gt; for every theme when the module loads forces the injection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Django wasn't serving Vite's &lt;code&gt;index.html&lt;/code&gt;.&lt;/strong&gt; The production page comes from Django's &lt;code&gt;templates/index.html&lt;/code&gt;, which resolves the hashed assets from Vite's manifest. Anything I added to Vite's &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; (the anti-flash script, Google Fonts) &lt;strong&gt;would never reach production&lt;/strong&gt;. It now lives in both files, and the keys have to match.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. A light theme exposes every hard-coded color.&lt;/strong&gt; Going from dark to light turned up three problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;color: 'white'&lt;/code&gt; on buttons became unreadable on the bright cyan accent. That gave me a new &lt;code&gt;onAccent&lt;/code&gt; token.&lt;/li&gt;
&lt;li&gt;Section wrappers with &lt;code&gt;backgroundColor: '$background'&lt;/code&gt; hid the aurora painted on &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;. They're &lt;code&gt;transparent&lt;/code&gt; now.&lt;/li&gt;
&lt;li&gt;The Radix &lt;code&gt;&amp;lt;Theme&amp;gt;&lt;/code&gt; paints its own background by default. It now uses &lt;code&gt;hasBackground={false}&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;docker compose restart&lt;/code&gt; served the old bundle.&lt;/strong&gt; The built &lt;code&gt;dist/&lt;/code&gt; is &lt;em&gt;copied into the image&lt;/em&gt;, so after &lt;code&gt;npm run build&lt;/code&gt; you need &lt;code&gt;up -d --build&lt;/code&gt;. I noticed because the &lt;code&gt;index-*.js&lt;/code&gt; hash in the HTML hadn't changed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2 — One headline, verified numbers
&lt;/h2&gt;

&lt;p&gt;While shipping the themes, I noticed something a recruiter would notice first. My portfolio said &lt;em&gt;"Backend Python Developer | Automation &amp;amp; Reliability"&lt;/em&gt;, my CV said &lt;em&gt;"Backend Python Developer · DevOps &amp;amp; Reliability"&lt;/em&gt;, and LinkedIn said something else. Three versions of the same person don't build trust.&lt;/p&gt;

&lt;p&gt;So LinkedIn became the source of truth: &lt;strong&gt;Backend Engineer → SRE | Python · Django · Kubernetes · Terraform · CI/CD&lt;/strong&gt;. The portfolio, the CV (versioned HTML → PDF via headless Edge) and my GitHub profile now say exactly that.&lt;/p&gt;

&lt;p&gt;Then I went further and &lt;strong&gt;re-measured every number I publish against the actual repositories&lt;/strong&gt; (&lt;code&gt;pytest --collect-only&lt;/code&gt;, &lt;code&gt;git shortlog&lt;/code&gt;, counting the Postman collection). Several were stale. The test count of my main automation project was off by thousands, a Postman collection had grown from 79 to 82 requests, and one coverage claim described a target instead of the 80% floor the CI actually enforces. I also caught my own mistake: I counted 18 releases when there were 19, because two versions only appear in the project's README, not in commit messages.&lt;/p&gt;

&lt;p&gt;The lesson: &lt;strong&gt;a number on your CV is a claim someone can check.&lt;/strong&gt; Measure it from the source, write down where it came from, and prefer wording like "2,800+ tests" when the exact count changes every week.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it ships
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;35 frontend tests&lt;/strong&gt; (Vitest + Testing Library) and &lt;strong&gt;49 backend tests (~91% coverage)&lt;/strong&gt; with mypy.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;scripts/verificar.sh&lt;/code&gt; in each repo &lt;strong&gt;replays the CI on a clean clone of HEAD&lt;/strong&gt;, with the same Node and Python versions as GitHub Actions. If Python 3.12 isn't installed locally, it runs inside a &lt;code&gt;python:3.12-slim&lt;/code&gt; container. The runner only sees what's committed, so that's what gets tested.&lt;/li&gt;
&lt;li&gt;End-to-end check in Docker (Django + Postgres + the embedded build) with Playwright: all three themes, desktop and 375 px mobile, no horizontal scroll.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CD with Jenkins&lt;/strong&gt;: test gate → image build → deploy → healthcheck → smoke test, with a stage that guarantees the deploy can never point at the production database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two themes besides Cian were ported from an internal tool I built earlier (&lt;a href="https://github.com/NicolasAndresCL/CmdVault" rel="noopener noreferrer"&gt;CmdVault&lt;/a&gt;), where they were NiceGUI palettes. Same idea, different stack. That's the part of side projects I enjoy most: the ideas move from one project to the next.&lt;/p&gt;

&lt;p&gt;If you try the site, tell me which theme you'd keep. And if you have your own trick for flash-free theming, I'd love to hear it. 👇&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftrjcqhhlv0lwdsmq0bsf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftrjcqhhlv0lwdsmq0bsf.png" alt="nicolasandrescl.pythonanywhere.com" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>django</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>From Local Scripts to Global-Ready Backend: CI/CD, Testing &amp; Coverage in SparkTrace</title>
      <dc:creator>Nicolás Andrés Cano Leal</dc:creator>
      <pubDate>Wed, 13 Aug 2025 05:37:28 +0000</pubDate>
      <link>https://dev.to/nicolasandrescl/from-local-scripts-to-global-ready-backend-cicd-testing-coverage-in-sparktrace-3p32</link>
      <guid>https://dev.to/nicolasandrescl/from-local-scripts-to-global-ready-backend-cicd-testing-coverage-in-sparktrace-3p32</guid>
      <description>&lt;p&gt;Over the past few weeks, I’ve transformed my backend project SparkTrace into a reproducible, test-driven, and CI/CD-integrated system—ready for international technical interviews and scalable deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Here’s a breakdown of the key milestones:
&lt;/h2&gt;

&lt;h2&gt;
  
  
  🧠 Modular Architecture &amp;amp; Refactor
&lt;/h2&gt;

&lt;p&gt;Reorganized Django commands into a clean structure under productos/management/commands/&lt;/p&gt;

&lt;p&gt;Decoupled logic for API integration, CSV loading, and Spark job execution&lt;/p&gt;

&lt;p&gt;Refactored settings and .gitignore for cross-platform compatibility&lt;/p&gt;

&lt;h2&gt;
  
  
  🧪 Automated Testing &amp;amp; Full Coverage
&lt;/h2&gt;

&lt;p&gt;Built a complete test suite with pytest and pytest-django&lt;/p&gt;

&lt;p&gt;Validated edge cases with JSON fixtures simulating real API responses&lt;/p&gt;

&lt;p&gt;Achieved 100% coverage in critical modules: CSV loader, API integration, custom exceptions&lt;/p&gt;

&lt;p&gt;Documented audit-ready testing strategy in bilingual README&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ CI/CD Integration with GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Configured workflow to run tests and coverage on every push and pull request&lt;/p&gt;

&lt;p&gt;Managed secrets (SECRET_KEY, CODECOV_TOKEN) securely via GitHub&lt;/p&gt;

&lt;p&gt;Integrated Codecov for visual coverage reporting&lt;/p&gt;

&lt;p&gt;Added badges for test status and coverage to README&lt;/p&gt;

&lt;h2&gt;
  
  
  🌐 Professional Presentation &amp;amp; Portfolio
&lt;/h2&gt;

&lt;p&gt;Updated bilingual README with technical branding&lt;/p&gt;

&lt;p&gt;Published progress on LinkedIn to showcase reproducibility and traceability&lt;/p&gt;

&lt;p&gt;Live portfolio available at nicolasandrescl.pythonanywhere.com&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 What’s Next?
&lt;/h2&gt;

&lt;p&gt;My goal is to lead backend projects with robust testing, API integration, and CI/CD pipelines that scale globally. SparkTrace is now a technical showcase for reproducibility, coverage, and auditability—ready for interviews, collaborations, and open-source contributions.&lt;/p&gt;

&lt;p&gt;🇪🇸 Versión en español&lt;/p&gt;

&lt;h2&gt;
  
  
  De scripts locales a backend listo para el mundo: CI/CD, testing y cobertura en SparkTrace
&lt;/h2&gt;

&lt;p&gt;Durante las últimas semanas, transformé mi proyecto backend SparkTrace en un sistema reproducible, orientado a testing y con integración CI/CD—preparado para entrevistas técnicas internacionales y despliegues escalables.&lt;/p&gt;

&lt;p&gt;Aquí el resumen de los hitos clave:&lt;/p&gt;

&lt;h2&gt;
  
  
  🧠 Arquitectura modular y refactor
&lt;/h2&gt;

&lt;p&gt;Reorganicé los comandos Django en una estructura limpia bajo productos/management/commands/&lt;/p&gt;

&lt;p&gt;Desacoplé la lógica de integración API, carga CSV y ejecución de jobs Spark&lt;/p&gt;

&lt;p&gt;Refactoricé los settings y .gitignore para compatibilidad multiplataforma&lt;/p&gt;

&lt;h2&gt;
  
  
  🧪 Testing automatizado y cobertura total
&lt;/h2&gt;

&lt;p&gt;Construí una suite completa con pytest y pytest-django&lt;/p&gt;

&lt;p&gt;Validé casos límite con fixtures JSON que simulan respuestas reales de API&lt;/p&gt;

&lt;p&gt;Alcancé cobertura del 100% en módulos críticos: cargador CSV, integración API, excepciones personalizadas&lt;/p&gt;

&lt;p&gt;Documenté la estrategia de testing lista para auditoría en README bilingüe&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%2F5tk4usqkve3x7m2unzy6.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%2F5tk4usqkve3x7m2unzy6.png" alt="tests" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ Integración CI/CD con GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Configuré workflow para ejecutar tests y cobertura en cada push y pull request&lt;/p&gt;

&lt;p&gt;Gestioné secretos (SECRET_KEY, CODECOV_TOKEN) de forma segura vía GitHub&lt;/p&gt;

&lt;p&gt;Integré Codecov para reporte visual de cobertura&lt;/p&gt;

&lt;p&gt;Añadí badges de estado de tests y cobertura al README&lt;/p&gt;

&lt;h2&gt;
  
  
  🌐 Presentación profesional y portafolio
&lt;/h2&gt;

&lt;p&gt;Actualicé README bilingüe con branding técnico&lt;/p&gt;

&lt;p&gt;Publiqué avances en LinkedIn destacando reproducibilidad y trazabilidad&lt;/p&gt;

&lt;p&gt;Portafolio en vivo disponible en nicolasandrescl.pythonanywhere.com&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 ¿Qué sigue?
&lt;/h2&gt;

&lt;p&gt;Mi objetivo es liderar proyectos backend con testing robusto, integración de APIs y pipelines CI/CD que escalen globalmente. SparkTrace es ahora una vitrina técnica de reproducibilidad, cobertura y auditoría—lista para entrevistas, colaboraciones y contribuciones open-source.&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%2Fo4cnqbu2rynaq5bds07k.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%2Fo4cnqbu2rynaq5bds07k.png" alt="test.yml" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>pyspark</category>
      <category>drf</category>
      <category>cicd</category>
      <category>pytest</category>
    </item>
    <item>
      <title>A Fresh Launch: My Developer Portfolio Is Production Ready</title>
      <dc:creator>Nicolás Andrés Cano Leal</dc:creator>
      <pubDate>Wed, 23 Jul 2025 16:25:33 +0000</pubDate>
      <link>https://dev.to/nicolasandrescl/a-fresh-launch-my-developer-portfolio-is-production-ready-2cp3</link>
      <guid>https://dev.to/nicolasandrescl/a-fresh-launch-my-developer-portfolio-is-production-ready-2cp3</guid>
      <description>&lt;p&gt;After a comprehensive refactor across both backend and frontend, my personal portfolio is now fully modular, scalable, and ready to go live.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 Backend: Django + DRF
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Migrated from monolithic to domain-based modular architecture&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RESTful endpoints for projects, skills, and contact functionality&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API documentation with Swagger via drf_spectacular&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Direct email dispatch via custom contact endpoint&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consistent routing under /api/, prepared for deployment&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsz9w8rjrhk0prg71tpsx.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%2Fsz9w8rjrhk0prg71tpsx.png" alt="Backend" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F596riqaba9o3r3mf3qcf.png" alt="Backend" width="800" height="450"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  🎨 Frontend: React + Vite
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Refactored components for visual consistency (ProjectCard, ContactCard, FooterCard)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connected contact form to backend with dynamic user feedback&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Added CV download functionality&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unified styles and hover states with centralized index.css&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Interactive menu with Material UI icons for GitHub, LinkedIn, and Dev.to&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbbnhjwxi1wtk9w227wgt.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%2Fbbnhjwxi1wtk9w227wgt.png" alt="Frontend" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  📁 All code is available on GitHub via the dev branch, and will soon replace my current portfolio version.
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;📎 Live preview: &lt;a href="https://nicolasandrescl.pythonanywhere.com" rel="noopener noreferrer"&gt;https://nicolasandrescl.pythonanywhere.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faa3hitd0bneoim1gcg1o.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%2Faa3hitd0bneoim1gcg1o.png" alt="Frontend" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06h8f3p0ljxvtoavc6oy.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%2F06h8f3p0ljxvtoavc6oy.png" alt="Frontend" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3ztbm43d5h0cknt1rvs.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%2Fj3ztbm43d5h0cknt1rvs.png" alt="Frontend" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
This project reflects not just code improvements, but an intentional evolution in architecture, documentation, and developer experience. It’s built to be shared, scaled, and showcased — and will be my new technical identity moving forward.&lt;/p&gt;

&lt;p&gt;🔖 Tags&lt;br&gt;
text&lt;/p&gt;

&lt;h1&gt;
  
  
  SoftwareEngineering #ReactDeveloper #BackendEngineer #DjangoREST #FrontendRefactor #APIDesign #ModularArchitecture #GitWorkflow #PortfolioLaunch #ProductionReady #OpenSource #LinkedInDev
&lt;/h1&gt;

</description>
      <category>softwareengineering</category>
      <category>react</category>
      <category>backend</category>
      <category>django</category>
    </item>
    <item>
      <title>Migrating My Portfolio from Django Templates to React + Vite + TailwindCSS</title>
      <dc:creator>Nicolás Andrés Cano Leal</dc:creator>
      <pubDate>Mon, 14 Jul 2025 01:00:37 +0000</pubDate>
      <link>https://dev.to/nicolasandrescl/migrating-my-portfolio-from-django-templates-to-react-vite-tailwindcss-3k6l</link>
      <guid>https://dev.to/nicolasandrescl/migrating-my-portfolio-from-django-templates-to-react-vite-tailwindcss-3k6l</guid>
      <description>&lt;p&gt;After years of building robust backend systems with Django, I’ve decided to give my personal portfolio a frontend makeover. The new version is built using React and Vite for a lightning-fast development experience, with TailwindCSS v4 (plus Shadcn/UI) for clean and flexible styling.&lt;/p&gt;

&lt;h2&gt;
  
  
  This transition reflects a shift not only in tech stack but in how I present my work as a Backend Developer — embracing modular architecture and focusing on user experience across devices.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  🛠️ Core Stack and Setup
&lt;/h2&gt;

&lt;p&gt;React + Vite: Fast dev server, intuitive routing, and modular component design.&lt;/p&gt;

&lt;p&gt;Tailwind v4: Manually integrated via &lt;a class="mentioned-user" href="https://dev.to/tailwindcss"&gt;@tailwindcss&lt;/a&gt;/postcss to support Shadcn/UI.&lt;/p&gt;

&lt;p&gt;Shadcn/UI: Powerful UI components with headless accessibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 Tailwind Setup Summary
&lt;/h2&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; @tailwindcss/postcss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With config files:&lt;/p&gt;

&lt;p&gt;postcss.config.cjs using &lt;a class="mentioned-user" href="https://dev.to/tailwindcss"&gt;@tailwindcss&lt;/a&gt;/postcss&lt;/p&gt;

&lt;p&gt;Tailwind purging content from src/*&lt;em&gt;/&lt;/em&gt;.{js,ts,jsx,tsx}&lt;/p&gt;

&lt;p&gt;Global styles defined in index.css for consistent theming&lt;/p&gt;

&lt;h2&gt;
  
  
  📁 Component Highlights
&lt;/h2&gt;

&lt;p&gt;ProjectCard.jsx, ContactCard.jsx, SkillCard.jsx, TituloCard.jsx — each crafted with purpose to highlight key areas of my portfolio&lt;/p&gt;

&lt;p&gt;Responsive layout powered by Tailwind utility classes&lt;/p&gt;

&lt;p&gt;ESLint and Vite configuration for clean code and optimized builds&lt;/p&gt;

&lt;h2&gt;
  
  
  🌐 Going Live Soon!
&lt;/h2&gt;

&lt;p&gt;The project will be deployed soon, but until then feel free to check out the current progress and explore my portfolio [insert your link here].&lt;/p&gt;

&lt;p&gt;Thanks for reading — if you’re working on frontend transitions or integrating Tailwind v4 in your own setup, let’s connect!&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%2Fgj3yvzcbfqmq5pon6kxd.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%2Fgj3yvzcbfqmq5pon6kxd.png" alt="Cambios Portafolio" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔗 #React #Vite #TailwindCSS #ShadcnUI #FrontendDev #PortfolioUpgrade #WebDevelopment #CleanArchitecture #Django #BackendToFrontend
&lt;/h2&gt;

</description>
      <category>react</category>
      <category>tailwindcss</category>
      <category>vite</category>
      <category>webdev</category>
    </item>
    <item>
      <title>🚀 Building a Flask RESTful API: From Jinja2 Views to a Scalable Backend</title>
      <dc:creator>Nicolás Andrés Cano Leal</dc:creator>
      <pubDate>Wed, 02 Jul 2025 08:57:32 +0000</pubDate>
      <link>https://dev.to/nicolasandrescl/building-a-flask-restful-api-from-jinja2-views-to-a-scalable-backend-4jm9</link>
      <guid>https://dev.to/nicolasandrescl/building-a-flask-restful-api-from-jinja2-views-to-a-scalable-backend-4jm9</guid>
      <description>&lt;p&gt;In this post, I’ll walk you through how I transitioned my Flask project from a classic Jinja2-based web app to a modular, production-ready backend with a RESTful API, full test coverage, and Swagger documentation.&lt;/p&gt;

&lt;p&gt;🧠 Motivation: I wanted to go beyond basic templating and learn how to build backends that scale, integrate with frontend frameworks, and support proper testing and documentation.&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%2Fs6zjm0kbur7nqdw5hu09.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%2Fs6zjm0kbur7nqdw5hu09.png" alt="swagger (Flasgger)" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 Technologies Used
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Python 3.12&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flask with Blueprint architecture&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SQLite + SQLAlchemy ORM&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flasgger (Swagger UI integration)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Jinja2 for server-rendered views&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pytest for automated testing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Postman for manual endpoint verification&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Git for version control&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💡 What I Built
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;🔄 A full RESTful API for task management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🧩 Clean code structure with an app factory (create_app) and Blueprint registration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🧪 Unit tests using Pytest with in-memory SQLite&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📘 Interactive API docs with Swagger&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🧼 Better endpoint handling using unique endpoint= values to resolve route conflicts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🧠 JSON-based error responses and safe exception management&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4au9kv6v7iux4fbharnd.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%2F4au9kv6v7iux4fbharnd.png" alt="Test Unitarios" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅ Results
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Swagger now correctly renders all documented endpoints.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;All tests pass reliably across isolated app instances.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The backend is ready to be consumed by frontend frameworks like React.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;All source code and documentation are publicly available via my portfolio.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔗 Check it out: nicolasandrescl.pythonanywhere.com 🧪 The code is already deployed as a static asset and will soon go live as a full API service.
&lt;/h2&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add JWT authentication&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connect a React frontend&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable pagination and filtering&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Deploy to production with metrics&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If you're learning Flask or building your first API, feel free to check out the repo and reach out—happy to collaborate and grow with the community!
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Python #Flask #RESTAPI #Swagger #Pytest #DeveloperJourney #WebDevelopment #Backend #SQLAlchemy #PortfolioProject
&lt;/h2&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>restapi</category>
      <category>swagger</category>
    </item>
    <item>
      <title>🚀 Evolving My FastAPI Project: Modular Architecture, Testing &amp; MySQL Integration</title>
      <dc:creator>Nicolás Andrés Cano Leal</dc:creator>
      <pubDate>Mon, 30 Jun 2025 05:55:28 +0000</pubDate>
      <link>https://dev.to/nicolasandrescl/evolving-my-fastapi-project-modular-architecture-testing-mysql-integration-g1</link>
      <guid>https://dev.to/nicolasandrescl/evolving-my-fastapi-project-modular-architecture-testing-mysql-integration-g1</guid>
      <description>&lt;p&gt;Over the past few hours, I've been deep into refactoring one of my FastAPI projects with a clear goal: make it production-ready and showcase it live through my portfolio at nicolasandrescl.pythonanywhere.com 💡&lt;/p&gt;




&lt;p&gt;Here’s what I accomplished:&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%2F4r2soffpjifntccim0yn.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%2F4r2soffpjifntccim0yn.png" alt="Fast Api" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ✅ What Changed
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Refactored the entire project structure into clearly separated layers:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;routers/ for endpoints&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;repository/ for data access logic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;schemas/ for Pydantic models&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;models/ for SQLAlchemy models&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;services/ for business logic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;core/ for configuration and DB connections&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Integrated a MySQL database using SQLAlchemy and PyMySQL for full persistence.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Switched to Pydantic v2, updating all .dict() references to .model_dump() to stay aligned with the latest standards.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Added error handling and validation, including custom messages and constraints (e.g. rejecting names under 3 characters).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wrote an integration test for user creation using pytest + TestClient, and installed httpx, email-validator, cryptography, and other missing dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Resolved merge conflicts cleanly and pushed the finalized version to GitHub.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Updated the README.md with architecture overview, setup instructions, and feature highlights.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔧 What’s Next?
&lt;/h2&gt;

&lt;p&gt;I’m preparing to deploy the API backend publicly so that visitors to my portfolio can see it in action. The /user/ endpoint will be testable directly from Swagger UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live site: 🌐 nicolasandrescl.pythonanywhere.com Repo: 📁 github.com/NicolasAndresCL/FastAPI
&lt;/h2&gt;

&lt;h2&gt;
  
  
  🛠️ Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;FastAPI, SQLAlchemy, PyMySQL&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MySQL, Pydantic v2, Uvicorn&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pytest, TestClient, HTTPX&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🧵 Let’s Connect
&lt;/h2&gt;

&lt;p&gt;If you're working on API design, backend structure, or transitioning to production, I'd love to exchange ideas!&lt;/p&gt;




&lt;h2&gt;
  
  
  FastAPI #BackendDevelopment #Python #SQLAlchemy #MySQL #TDD #CleanArchitecture #pytest #DevJourney #Pydantic #PythonBackend #PortfolioDev #APIDesign #OpenSource #PythonAnywhere
&lt;/h2&gt;

</description>
      <category>fastapi</category>
      <category>backenddevelopment</category>
      <category>python</category>
      <category>sqlalchemy</category>
    </item>
    <item>
      <title>🚀 My Developer Portfolio Is Live and Now Fully Responsive!</title>
      <dc:creator>Nicolás Andrés Cano Leal</dc:creator>
      <pubDate>Sat, 28 Jun 2025 01:05:15 +0000</pubDate>
      <link>https://dev.to/nicolasandrescl/my-developer-portfolio-is-live-and-now-fully-responsive-mb9</link>
      <guid>https://dev.to/nicolasandrescl/my-developer-portfolio-is-live-and-now-fully-responsive-mb9</guid>
      <description>&lt;p&gt;Bringing your work into production is thrilling—until something breaks and you don’t know why. That’s exactly what I faced while deploying my Django-based portfolio.&lt;/p&gt;

&lt;p&gt;Despite making all the right changes in my HTML and CSS, nothing was updating on the live site. Turns out, the answer was hidden in a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bash
python manage.py collectstatic --noinput
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  🎯 What I Built
&lt;/h1&gt;

&lt;p&gt;A full portfolio site running on PythonAnywhere, styled with custom CSS (nude color palette for minimalist elegance)&lt;/p&gt;

&lt;p&gt;Built with Django and Django REST Framework, showing off real API endpoints and dynamic content&lt;/p&gt;

&lt;p&gt;Responsive design using Flexbox, Grid, and media queries&lt;/p&gt;

&lt;p&gt;Deployment-ready, including secure admin access and custom favicon&lt;/p&gt;

&lt;h1&gt;
  
  
  📸 Key Win: Debugging in Production
&lt;/h1&gt;

&lt;p&gt;The image in my header was overlapping the page title on mobile. After reviewing my layout and adding all the right media queries… the issue persisted 😩 But the problem wasn’t layout—it was the old static files being served by Django. Once I re-ran collectstatic inside my virtual environment, everything fell into place.&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%2Fqp1z729k00pqem3mlaky.jpeg" 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%2Fqp1z729k00pqem3mlaky.jpeg" alt="Imagen de Error" width="720" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwx2yrnvjc9afnopq9gks.jpeg" 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%2Fwx2yrnvjc9afnopq9gks.jpeg" alt="Imagen Mejora" width="720" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  🎨 See It for Yourself
&lt;/h1&gt;

&lt;p&gt;🌐 nicolasandrescl.pythonanywhere.com Feel free to check it out and explore my featured projects. Feedback is always welcome!&lt;/p&gt;




&lt;h1&gt;
  
  
  Django #Python #BackendDeveloper #WebDevelopment #ResponsiveDesign #PortfolioSite #ProductionDebugging #PythonAnywhere #DevJourney #OpenToWork
&lt;/h1&gt;

</description>
      <category>productiondebugging</category>
      <category>responsivedesign</category>
      <category>drf</category>
      <category>webdev</category>
    </item>
    <item>
      <title>🚀 Successfully Deployed: My Django Portfolio is Now Live on PythonAnywhere</title>
      <dc:creator>Nicolás Andrés Cano Leal</dc:creator>
      <pubDate>Fri, 27 Jun 2025 19:08:01 +0000</pubDate>
      <link>https://dev.to/nicolasandrescl/successfully-deployed-my-django-portfolio-is-now-live-on-pythonanywhere-830</link>
      <guid>https://dev.to/nicolasandrescl/successfully-deployed-my-django-portfolio-is-now-live-on-pythonanywhere-830</guid>
      <description>&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%2Fl0tddxgljqniz878vk9n.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%2Fl0tddxgljqniz878vk9n.png" alt="Portafolio" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
After weeks of focused development, troubleshooting, and iteration, I’m proud to share that my Django-based portfolio project — MiPortafolioDjango — is now fully deployed and live in production!&lt;/p&gt;

&lt;p&gt;🔗 Check it out here: nicolasandrescl.pythonanywhere.com&lt;/p&gt;

&lt;p&gt;This deployment involved much more than pushing code — it was a comprehensive hands-on learning experience covering configuration management, static and media file handling, security, and debugging in a real-world production environment.&lt;/p&gt;




&lt;h1&gt;
  
  
  ⚙️ Key Improvements and Configurations
&lt;/h1&gt;

&lt;p&gt;I. Django Configuration (settings.py &amp;amp; wsgi.py)&lt;br&gt;
🔐 Secure Key Management with python-decouple&lt;/p&gt;

&lt;p&gt;Sensitive data like SECRET_KEY and EMAIL_HOST_PASSWORD were externalized using the python-decouple package.&lt;/p&gt;

&lt;p&gt;A .env file was created at the root of the project to store these secrets securely.&lt;/p&gt;

&lt;p&gt;The wsgi.py file was modified to explicitly load environment variables using pathlib.Path and config() from decouple.&lt;/p&gt;

&lt;h2&gt;
  
  
  📁 Static Files Configuration
&lt;/h2&gt;

&lt;p&gt;Set STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') in settings.py.&lt;/p&gt;

&lt;p&gt;Ran python manage.py collectstatic to collect and serve static files on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  🖼 Media Files Configuration
&lt;/h2&gt;

&lt;p&gt;Configured MEDIA_URL and MEDIA_ROOT to handle uploaded files using Django’s ImageField.&lt;/p&gt;

&lt;p&gt;This enabled uploaded images to be visible through the admin and rendered on the frontend.&lt;/p&gt;

&lt;h2&gt;
  
  
  🌍 Production Settings
&lt;/h2&gt;

&lt;p&gt;ALLOWED_HOSTS was updated to include nicolasandrescl.pythonanywhere.com.&lt;/p&gt;

&lt;p&gt;Set DEBUG = False for security in production.&lt;/p&gt;

&lt;p&gt;Confirmed that portfolio_project/wsgi.py contains necessary .env loading logic to work independently of PythonAnywhere’s default.&lt;/p&gt;

&lt;h1&gt;
  
  
  II. Server Setup on PythonAnywhere
&lt;/h1&gt;

&lt;p&gt;✅ Repository Sync: Cleaned and re-pushed the latest repo to GitHub, then pulled it into PythonAnywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  🐍 Virtual Environment: Activated the virtualenv properly using workon MiPortafolioDjango_env.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  🛠 Migrations &amp;amp; Superuser: Ran migrate and created a superuser for admin access.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  🗂 Static &amp;amp; Media Files Web Config: Added /static/ and /media/ mappings in the Web &amp;gt; Static Files section.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  🔁 App Reloads: Performed necessary web app reloads after every major change.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  🧠 Advanced Debugging: Inspected error.log and server.log for resolving tricky deployment issues.
&lt;/h2&gt;

&lt;p&gt;🐛 Critical Fixes Before Production Launch&lt;br&gt;
🔄 GitHub URL Redirection Bug&lt;/p&gt;

&lt;p&gt;Fixed a bug where all project cards linked to the same (wrong) GitHub repo.&lt;/p&gt;

&lt;p&gt;Root cause: hardcoded URLs in the HTML template.&lt;/p&gt;

&lt;p&gt;Solution: replaced with {{ project.github_link }} to dynamically fetch the correct link.&lt;/p&gt;

&lt;h2&gt;
  
  
  🖼 Project Image Display Issues
&lt;/h2&gt;

&lt;p&gt;Solved the problem where project images didn’t render on the frontend.&lt;/p&gt;

&lt;p&gt;Verified correct use of ImageField and ensured MEDIA_URL and MEDIA_ROOT were properly configured.&lt;/p&gt;

&lt;p&gt;Now images uploaded via Django Admin appear seamlessly on the site.&lt;/p&gt;




&lt;h1&gt;
  
  
  🧩 Conclusion
&lt;/h1&gt;

&lt;p&gt;This deployment marks a significant step forward in my journey as a backend developer. The process not only strengthened my understanding of Django and server-side deployment, but also gave me real DevOps exposure by working directly with environment variables, file systems, and web server configurations.&lt;/p&gt;

&lt;p&gt;🔍 Every issue resolved and each configuration tweaked is now a new skill in my toolkit.&lt;br&gt;
Check it out, and feel free to leave feedback!&lt;/p&gt;




&lt;h1&gt;
  
  
  Django #Python #Deployment #PythonAnywhere #BackendDevelopment #DevOps #PortfolioProject #WebDevelopment #DjangoTips #FullStackJourney #OpenToWork
&lt;/h1&gt;

</description>
      <category>django</category>
      <category>devops</category>
      <category>pythonanywhere</category>
      <category>backenddevelopment</category>
    </item>
    <item>
      <title>Taking My Django Portfolio to Production – Key Milestones Achieved 🚀</title>
      <dc:creator>Nicolás Andrés Cano Leal</dc:creator>
      <pubDate>Thu, 26 Jun 2025 03:45:58 +0000</pubDate>
      <link>https://dev.to/nicolasandrescl/taking-my-django-portfolio-to-production-key-milestones-achieved-166f</link>
      <guid>https://dev.to/nicolasandrescl/taking-my-django-portfolio-to-production-key-milestones-achieved-166f</guid>
      <description>&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%2Frjrke2qfmacfk79c8ddq.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%2Frjrke2qfmacfk79c8ddq.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;After weeks of development and iterations, I’m excited to share the latest progress on my personal portfolio, built with Django and Django REST Framework. Here's a breakdown of the key improvements I've implemented before deploying the project to production.&lt;/p&gt;




&lt;p&gt;🔐 1. Fully Functional Contact Form with Secure Email Integration&lt;br&gt;
One of the core features I prioritized was enabling visitors to contact me directly from the website. I implemented a working contact form that sends emails securely using a Google App Password, protecting my credentials while ensuring deliverability.&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%2F4q3w4nzlo0lviwm3w779.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%2F4q3w4nzlo0lviwm3w779.png" alt="Form" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;🎨 2. Enhanced UI/UX – Visual and Structural Polish&lt;br&gt;
To make the user experience smoother and more visually coherent, I refined the CSS styling, improved background color contrast, and reorganized image placements. These changes make the navigation feel more intuitive and professional.&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%2Farurqde14hd7weasz53z.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%2Farurqde14hd7weasz53z.png" alt="estilo" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;📘 3. Embedded API Documentation (Swagger &amp;amp; ReDoc)&lt;br&gt;
Since the backend is powered by Django REST Framework, I integrated auto-generated API documentation using Swagger UI and ReDoc, accessible directly from the site’s main interface. This allows other developers to explore all endpoints with ease.&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%2Fxbwe4msnbcgmtjed63mm.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%2Fxbwe4msnbcgmtjed63mm.png" alt="swagger" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftd6oa3lh3nmdwhne0ynj.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%2Ftd6oa3lh3nmdwhne0ynj.png" alt="swagger" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwtngb8289axyzebxa82.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%2Fdwtngb8289axyzebxa82.png" alt="redoc" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;⚙️ 4. Production-Ready Configuration&lt;br&gt;
I’ve fine-tuned the project settings for deployment:&lt;/p&gt;

&lt;p&gt;DEBUG=False to secure the environment&lt;/p&gt;

&lt;p&gt;Proper configuration of STATICFILES and MEDIA handling&lt;/p&gt;

&lt;p&gt;Optimized the settings.py file to separate development and production environments&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%2F45iwzbqonmjh0852o1u6.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%2F45iwzbqonmjh0852o1u6.png" alt="Debuf=False" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;🚀 5. Preparing for Deployment on PythonAnywhere&lt;br&gt;
The final step is to host both frontend and backend on PythonAnywhere, which will allow seamless access and easier management under one domain. This transition is already in progress.&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%2Fq7aj239f5xryrolydzhe.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%2Fq7aj239f5xryrolydzhe.png" alt="project" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;🔍 What’s Next?&lt;br&gt;
Soon, I’ll announce the complete deployment and share the final hosted version. Until then, you can preview the current progress and code here:&lt;br&gt;
👉 &lt;a href="https://nicolasandrescl.github.io" rel="noopener noreferrer"&gt;https://nicolasandrescl.github.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe10dep8ljazfhjp6khy7.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%2Fe10dep8ljazfhjp6khy7.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Thanks for following along in my journey! If you have feedback, ideas, or just want to connect — I’m always open to learning from the community.&lt;/p&gt;




&lt;p&gt;Tags:&lt;/p&gt;

&lt;h1&gt;
  
  
  Django #Python #Portfolio #WebDevelopment #APIDocumentation #SoftwareEngineering #PythonAnywhere #FullStack
&lt;/h1&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>swgger</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>🚀 Strengthening My Django Portfolio: Security, Contact Integration, and Optimization</title>
      <dc:creator>Nicolás Andrés Cano Leal</dc:creator>
      <pubDate>Wed, 25 Jun 2025 07:48:39 +0000</pubDate>
      <link>https://dev.to/nicolasandrescl/strengthening-my-django-portfolio-security-contact-integration-and-optimization-3f46</link>
      <guid>https://dev.to/nicolasandrescl/strengthening-my-django-portfolio-security-contact-integration-and-optimization-3f46</guid>
      <description>&lt;p&gt;🔐 1. API Security with Token Authentication&lt;br&gt;
One of the key updates was reinforcing the security of my Django REST API.&lt;/p&gt;

&lt;p&gt;I've implemented TokenAuthentication along with IsAuthenticatedOrReadOnly permissions. This means:&lt;/p&gt;

&lt;p&gt;Visitors can freely read and explore public endpoints.&lt;/p&gt;

&lt;p&gt;Only authenticated users (in this case, myself) with a valid token can perform write operations (POST, PUT, DELETE).&lt;/p&gt;

&lt;p&gt;Why it matters:&lt;br&gt;
This approach ensures that my API remains accessible and transparent while protecting against unauthorized modifications. It's a great foundation for future scaling or integration with frontend frameworks.&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%2Flhphbd5bsn9wz20dn6o4.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%2Flhphbd5bsn9wz20dn6o4.png" alt="ADMIN" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flqw86o487gjkdu4em3lr.png" alt="SWAGGER UI" width="800" height="450"&gt;
&lt;/h2&gt;

&lt;p&gt;📬 2. Functional Contact Form&lt;br&gt;
I added a functional and stylish contact form to the main page of my portfolio.&lt;/p&gt;

&lt;p&gt;It uses Django’s form handling and CSRF protection, and sends messages securely via email or saves them to a database for future reference.&lt;/p&gt;

&lt;p&gt;Why it matters:&lt;br&gt;
A direct and professional communication channel is essential for networking, job opportunities, and collaborations. This adds a serious layer of usability to my site.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcxstt0rbwf4snxc51ecl.png" alt="FORM" width="800" height="450"&gt;
&lt;/h2&gt;

&lt;p&gt;🧱 3. Optimized Static File Management&lt;br&gt;
Previously, my static files (like CSS and images) were scattered and unorganized.&lt;/p&gt;

&lt;p&gt;I restructured them using Django’s STATICFILES_DIRS and collectstatic system, keeping all assets organized inside the app directories and ensuring they’re properly served in production.&lt;/p&gt;

&lt;p&gt;Why it matters:&lt;br&gt;
A clean static structure avoids conflicts, speeds up deployment, and improves maintainability of the project as it grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgr95qxs8ol3mhmgj2dtv.png" alt="FRONTEND" width="800" height="450"&gt;
&lt;/h2&gt;

&lt;p&gt;📝 4. Clean Git Commit History&lt;br&gt;
Every change has been tracked using clear and descriptive Git commits, following best practices like:&lt;/p&gt;

&lt;p&gt;Atomic commits&lt;/p&gt;

&lt;p&gt;Useful commit messages (add, fix, refactor, etc.)&lt;/p&gt;

&lt;p&gt;Grouped by functionality&lt;/p&gt;

&lt;p&gt;You can review the repository here 👉 GitHub Repo&lt;/p&gt;

&lt;p&gt;Why it matters:&lt;br&gt;
A well-documented Git history reflects professionalism and helps collaborators (or future-me!) understand the project’s evolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsf48nkgqjwfge6hjerre.png" alt="COMMIT" width="800" height="450"&gt;
&lt;/h2&gt;

&lt;p&gt;✅ What’s Next?&lt;br&gt;
Production deployment on a custom domain.&lt;/p&gt;

&lt;p&gt;More automation using CI/CD.&lt;/p&gt;

&lt;p&gt;Deployment of a full-featured blog using Django CMS or a headless approach with DRF + React.&lt;/p&gt;

&lt;p&gt;Live site 👉 nicolasandrescl.github.io&lt;/p&gt;

&lt;p&gt;Thanks for reading, and feel free to connect or leave suggestions! 🙌&lt;br&gt;
Let’s keep learning and building 💪&lt;/p&gt;




&lt;h1&gt;
  
  
  django #python #webdev #portfolio #api #backend #devjournal #fullstack #restapi #programming
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>portfolio</category>
      <category>backend</category>
      <category>api</category>
    </item>
    <item>
      <title>🚀 Migrating from SQLite3 to MySQL in My Django REST API – Step by Step</title>
      <dc:creator>Nicolás Andrés Cano Leal</dc:creator>
      <pubDate>Mon, 23 Jun 2025 08:14:13 +0000</pubDate>
      <link>https://dev.to/nicolasandrescl/migrating-from-sqlite3-to-mysql-in-my-django-rest-api-step-by-step-34pl</link>
      <guid>https://dev.to/nicolasandrescl/migrating-from-sqlite3-to-mysql-in-my-django-rest-api-step-by-step-34pl</guid>
      <description>&lt;p&gt;🛠️ Step-by-step Migration Process&lt;br&gt;
✅ 1. Install MySQL Driver&lt;br&gt;
Inside my virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install mysqlclient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Alternatively, you can use pymysql if you face issues with mysqlclient.)&lt;/p&gt;




&lt;p&gt;✅ 2. Create a New MySQL Database&lt;br&gt;
Using MySQL Workbench or CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE DATABASE myproject_db CHARACTER SET UTF8MB4;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;✅ 3. Update settings.py&lt;br&gt;
I replaced the default SQLite configuration with MySQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'myproject_db',
        'USER': 'root',
        'PASSWORD': 'your_password',
        'HOST': 'localhost',
        'PORT': '3306',
        'OPTIONS': {
            'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
        },
    }
}

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

&lt;/div&gt;






&lt;p&gt;✅ 4. Apply Migrations&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py makemigrations
python manage.py migrate

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

&lt;/div&gt;






&lt;p&gt;✅ 5. Create a New Superuser&lt;br&gt;
Since the database is clean, I recreated the admin user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py createsuperuser

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

&lt;/div&gt;






&lt;p&gt;✅ 6. Test Functionality&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;All API endpoints were tested via Postman.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Admin panel access was verified.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Existing unit tests were re-run to confirm stability.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvv7xctwjc5mfszavhe0.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%2Fnvv7xctwjc5mfszavhe0.png" alt="MySQL" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fawsnhjt8ftthfhfq25ll.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%2Fawsnhjt8ftthfhfq25ll.png" alt="Admin Customers" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;✅ 7. Optional: Migrate data from SQLite to MySQL&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py dumpdata &amp;gt; data.json
# after switching DB
python manage.py loaddata data.json

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

&lt;/div&gt;






&lt;p&gt;🔗 My Work Online&lt;br&gt;
🔹 Portfolio: &lt;a href="https://nicolasandrescl.github.io/" rel="noopener noreferrer"&gt;https://nicolasandrescl.github.io/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  🔹 GitHub Repo: &lt;a href="https://github.com/NicolasAndresCL/API_con_DRF" rel="noopener noreferrer"&gt;https://github.com/NicolasAndresCL/API_con_DRF&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;💬 Final Thoughts&lt;br&gt;
This migration helped me take my backend to the next level. Whether it's for performance, structure, or readiness for deployment, using MySQL brings me closer to a professional production setup.&lt;/p&gt;

&lt;p&gt;Would love to hear your thoughts – which DB do you prefer for your Django projects?&lt;/p&gt;

</description>
      <category>djangorestframework</category>
      <category>mysql</category>
      <category>api</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Mastering Full-Stack Authentication with Django REST Framework and React ⚙️🔐</title>
      <dc:creator>Nicolás Andrés Cano Leal</dc:creator>
      <pubDate>Sat, 21 Jun 2025 01:24:35 +0000</pubDate>
      <link>https://dev.to/nicolasandrescl/mastering-full-stack-authentication-with-django-rest-framework-and-react-4k30</link>
      <guid>https://dev.to/nicolasandrescl/mastering-full-stack-authentication-with-django-rest-framework-and-react-4k30</guid>
      <description>&lt;p&gt;&lt;em&gt;From a blank screen to a fully functional login flow — this has been one of the most rewarding challenges in my personal project.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;🚀 Introduction&lt;br&gt;
Not long ago, I tackled one of the most classic challenges in modern full-stack development: implementing user authentication.&lt;br&gt;
With Django REST Framework (DRF) powering the backend and React + Vite on the frontend, the journey was packed with misconfigured routes, unexpected 405 errors, and the occasional blank screen.&lt;/p&gt;

&lt;p&gt;In this post, I’ll share my experience, the bugs I faced, what I learned, and how I eventually solved everything.&lt;/p&gt;

&lt;p&gt;🔍 The Problem: Unexpected Errors and Misleading Routes&lt;br&gt;
After setting up the login form and connecting React to the API, I ran into this all-too-familiar message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;405 Method Not Allowed  
Unexpected non-whitespace character after JSON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eventually, I discovered I was sending credentials to the wrong endpoint (/api/schema/swagger-ui/) instead of the actual auth route (/api/token/).&lt;br&gt;
A small mistake… but it caused a lot of frustration.&lt;/p&gt;

&lt;p&gt;✍️ Lesson Learned&lt;br&gt;
Double-check your routes, HTTP methods, and urls.py configurations. And never underestimate console errors — they’re often trying to help you.&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%2Fjsk65wq7l1ejtu8xk8ev.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%2Fjsk65wq7l1ejtu8xk8ev.png" alt="Login" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🧩 The Fix: Connecting the Dots, Managing State, and Redirecting&lt;br&gt;
Once I got the token successfully, I stored it in localStorage and used React Router’s useNavigate() to redirect the user to the customer dashboard.&lt;/p&gt;

&lt;p&gt;Along the way, I cleaned up and modularized the frontend:&lt;/p&gt;

&lt;p&gt;Split key views (Login, Dashboard) into src/pages/&lt;/p&gt;

&lt;p&gt;Used CSS Modules for component-scoped styling&lt;/p&gt;

&lt;p&gt;Added loading indicators and error handling to the login form&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%2F39h1e0aidjb1ohpbc62v.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%2F39h1e0aidjb1ohpbc62v.png" alt="Deshboard" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (response.ok) {
  localStorage.setItem('authToken', data.token);
  navigate('/customers');
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result: a smoother user experience and more maintainable code.&lt;/p&gt;

&lt;p&gt;🛠️ Extras: React + CSS Modules&lt;br&gt;
Beyond authentication, I also worked on the dashboard layout and organized my styles into .module.css files per page. This approach:&lt;/p&gt;

&lt;p&gt;Makes teamwork easier&lt;/p&gt;

&lt;p&gt;Prevents global style conflicts&lt;/p&gt;

&lt;p&gt;Sets the stage for future scalability&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%2F1p4gv892ep0o58vgulhk.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%2F1p4gv892ep0o58vgulhk.png" alt="Deshboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🎯 Next Steps&lt;br&gt;
Implement protected routes for authenticated users&lt;/p&gt;

&lt;p&gt;Centralize authentication state management&lt;/p&gt;

&lt;p&gt;Continue building CRUD functionality for Customers, Orders, and Products&lt;/p&gt;

&lt;p&gt;🎉 Conclusion&lt;br&gt;
Every bug taught me something new. This experience not only improved my logic and debugging skills, but also refined my approach to clean code and user-centered design.&lt;/p&gt;

&lt;p&gt;👉 You can follow the full project here: nicolasandrescl.github.io&lt;/p&gt;

&lt;p&gt;Have you faced similar authentication struggles in your full-stack projects? I’d love to hear your thoughts in the comments!&lt;/p&gt;

</description>
      <category>fullstack</category>
      <category>react</category>
      <category>djangorestframework</category>
      <category>authentication</category>
    </item>
  </channel>
</rss>
