<?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: Joseph Ndungi</title>
    <description>The latest articles on DEV Community by Joseph Ndungi (@josephndungi).</description>
    <link>https://dev.to/josephndungi</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%2F1078711%2F54440aaf-5d5d-4e95-82ae-3958b81d1011.jpeg</url>
      <title>DEV Community: Joseph Ndungi</title>
      <link>https://dev.to/josephndungi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/josephndungi"/>
    <language>en</language>
    <item>
      <title>Building Plots in Angular for Data Visualization</title>
      <dc:creator>Joseph Ndungi</dc:creator>
      <pubDate>Tue, 24 Mar 2026 08:37:18 +0000</pubDate>
      <link>https://dev.to/josephndungi/building-plots-in-angular-for-data-visualization-487j</link>
      <guid>https://dev.to/josephndungi/building-plots-in-angular-for-data-visualization-487j</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Data is easier to understand when you can see it. Tables are useful, but charts help you quickly spot trends, patterns, and outliers. In this post, I will share how I built plots in Angular for a linear regression tool and some lessons I learned along the way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why plots matter
&lt;/h2&gt;

&lt;p&gt;When working with models like linear regression, numbers alone are not enough. You need visuals to answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are predictions close to actual values&lt;/li&gt;
&lt;li&gt;Are errors randomly distributed&lt;/li&gt;
&lt;li&gt;Are there outliers affecting the model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plots such as residual charts and QQ plots make this very clear.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choosing a plotting library
&lt;/h2&gt;

&lt;p&gt;In Angular, one of the easiest ways to create charts is by using Plotly.&lt;/p&gt;

&lt;p&gt;Plotly works well because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It supports many chart types&lt;/li&gt;
&lt;li&gt;It is interactive out of the box&lt;/li&gt;
&lt;li&gt;It works nicely with Angular components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To use it, install the Angular wrapper:&lt;br&gt;
&lt;/p&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;plotly.js angular-plotly.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then register it in your module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PlotlyModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;angular-plotly.js&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="nd"&gt;NgModule&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;imports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PlotlyModule&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Preparing your data
&lt;/h2&gt;

&lt;p&gt;Most APIs return data as arrays of objects. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Fitted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Residual&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Fitted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Residual&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.2&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;Before plotting, you need to map this into arrays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&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;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fitted&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;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&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;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Residual&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 a very common step when working with charts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Creating a simple plot
&lt;/h2&gt;

&lt;p&gt;Here is an example of a residual plot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;createResidualPlot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Fitted&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Residual&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;markers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scatter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Residuals&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;layout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Residuals vs Fitted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;xaxis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fitted Values&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="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;yaxis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Residuals&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="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;residualPlot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="nx"&gt;layout&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in your HTML:&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;plotly-plot&lt;/span&gt;
  &lt;span class="na"&gt;[data]=&lt;/span&gt;&lt;span class="s"&gt;"residualPlot.data"&lt;/span&gt;
  &lt;span class="na"&gt;[layout]=&lt;/span&gt;&lt;span class="s"&gt;"residualPlot.layout"&lt;/span&gt;
  &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"width: 100%; height: 400px"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plotly-plot&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Supporting multiple plots
&lt;/h2&gt;

&lt;p&gt;As your app grows, you may need several plots:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Residuals vs fitted&lt;/li&gt;
&lt;li&gt;Actual vs fitted&lt;/li&gt;
&lt;li&gt;QQ plot&lt;/li&gt;
&lt;li&gt;Cook’s distance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good approach is to create one function per plot and store each result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;residualPlot&lt;/span&gt; &lt;span class="o"&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;qqPlot&lt;/span&gt; &lt;span class="o"&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;actualVsFittedPlot&lt;/span&gt; &lt;span class="o"&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 keeps your code clean and easy to manage.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling multiple datasets
&lt;/h2&gt;

&lt;p&gt;One challenge I faced was supporting multiple securities. Each one had its own set of plots.&lt;/p&gt;

&lt;p&gt;Instead of overwriting data, I stored them in a map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;visualizationMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;security&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;visualization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then when the user selects a security, I load its plots.&lt;/p&gt;




&lt;h2&gt;
  
  
  Improving user experience
&lt;/h2&gt;

&lt;p&gt;At first, all plots were shown in the main screen. This worked for one dataset but became messy with many.&lt;/p&gt;

&lt;p&gt;A better solution was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a graph button in the table&lt;/li&gt;
&lt;li&gt;Open plots inside a popup&lt;/li&gt;
&lt;li&gt;Show charts only when needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps the interface clean and focused.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common issues to watch out for
&lt;/h2&gt;

&lt;p&gt;Here are a few things that can go wrong:&lt;/p&gt;

&lt;h3&gt;
  
  
  Undefined data
&lt;/h3&gt;

&lt;p&gt;If you try to access &lt;code&gt;data.Fitted&lt;/code&gt; instead of mapping an array, your plot will not render.&lt;/p&gt;

&lt;h3&gt;
  
  
  Missing module
&lt;/h3&gt;

&lt;p&gt;If Angular does not recognize &lt;code&gt;plotly-plot&lt;/code&gt;, make sure you imported &lt;code&gt;PlotlyModule&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Adding plots to an Angular app can greatly improve how users understand data. With a library like Plotly, you can build powerful and interactive visuals with little effort.&lt;/p&gt;

&lt;p&gt;Start simple. Focus on one plot. Then grow your solution step by step.&lt;/p&gt;

&lt;p&gt;In my case, moving charts into a popup and supporting multiple datasets made a big difference in usability.&lt;/p&gt;

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

</description>
      <category>angular</category>
      <category>datascience</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Implementing OpenID Connect Authentication in Angular 19 Without NgModules</title>
      <dc:creator>Joseph Ndungi</dc:creator>
      <pubDate>Thu, 26 Feb 2026 11:48:14 +0000</pubDate>
      <link>https://dev.to/josephndungi/implementing-openid-connect-authentication-in-angular-19-without-ngmodules-ia8</link>
      <guid>https://dev.to/josephndungi/implementing-openid-connect-authentication-in-angular-19-without-ngmodules-ia8</guid>
      <description>&lt;p&gt;Authentication is one of those things that looks simple from the outside and then quickly humbles you once you start wiring it properly.&lt;/p&gt;

&lt;p&gt;Over the past day, I implemented OpenID Connect authentication in an Angular 19 application using Node v20.18.3. I am not using NgModules. The entire app is built using standalone APIs, which makes things cleaner but also slightly different from older tutorials you may find online.&lt;/p&gt;

&lt;p&gt;This post walks through the setup, the structure, the issues I ran into, and how I fixed them.&lt;/p&gt;

&lt;p&gt;Tech stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node v20.18.3&lt;/li&gt;
&lt;li&gt;Angular 19.2.20&lt;/li&gt;
&lt;li&gt;angular auth oidc client&lt;/li&gt;
&lt;li&gt;Identity Server as the OpenID provider&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No NgModules. Everything is standalone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1. Install the OIDC client
&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;angular-auth-oidc-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This library handles the heavy lifting. Token exchange. Storage. Refresh. State handling. You should not manually handle tokens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2. Configure authentication in app.config.ts
&lt;/h2&gt;

&lt;p&gt;Because this is a standalone Angular app, everything is configured using ApplicationConfig.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;provideAppInitializer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inject&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;provideRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/router&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;provideHttpClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@angular/common/http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AuthModule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StsConfigLoader&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;angular-auth-oidc-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app.routes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;appConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApplicationConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;provideRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;provideHttpClient&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;

    &lt;span class="nf"&gt;importProvidersFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;AuthModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRoot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StsConfigLoader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;useFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OidcConfigLoaderFactory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="nf"&gt;provideAppInitializer&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;oidcSecurityService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OidcSecurityService&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;oidcSecurityService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkAuth&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toPromise&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;result&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&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="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important detail.&lt;/p&gt;

&lt;p&gt;Calling checkAuth on app startup is mandatory. Without it, the redirect from Identity Server will not be processed and login will look broken.&lt;/p&gt;

&lt;p&gt;This was the first major issue I faced.&lt;/p&gt;

&lt;p&gt;Login seemed to work. Tokens were returned. But Angular never picked them up. The fix was adding checkAuth in provideAppInitializer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3. OIDC configuration
&lt;/h2&gt;

&lt;p&gt;In your config loader:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;authority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://your-identity-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;redirectUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;postLogoutRedirectUri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-client-id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;openid profile api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;responseType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;silentRenew&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="nx"&gt;useRefreshToken&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always use responseType code with PKCE.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4. Implement login
&lt;/h2&gt;

&lt;p&gt;Your login service can be simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&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;oidcSecurityService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;customParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why prompt equals login?&lt;/p&gt;

&lt;p&gt;This was another issue I ran into.&lt;/p&gt;

&lt;p&gt;The app was not redirecting to the Identity Server login page. It was silently authenticating and returning tokens.&lt;/p&gt;

&lt;p&gt;The reason was that I already had an active SSO session. The Identity Server detected the session and immediately redirected back without showing the login screen.&lt;/p&gt;

&lt;p&gt;Adding prompt equals login forces the login page to appear.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5. Guard your routes properly
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Routes&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;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoginComponent&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LayoutComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;canActivate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AuthorizationGuard&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;redirectTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pathMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;full&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="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DashboardComponent&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;management&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LicenceManagementComponent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;redirectTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;canActivate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Observable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;UrlTree&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oidcSecurityService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkAuth&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;pipe&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;isAuthenticated&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&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="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;Another issue I encountered here was destructuring incorrectly.&lt;/p&gt;

&lt;p&gt;My custom isAuthenticated method was already returning a boolean, but in the guard I destructured it as if it was an object. That silently broke navigation and kept redirecting back to login.&lt;/p&gt;

&lt;p&gt;Fixing the observable type fixed the routing loop.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6. Redirect after login
&lt;/h2&gt;

&lt;p&gt;After authentication, Angular returns to whatever URL was used as redirectUrl.&lt;/p&gt;

&lt;p&gt;If that is login, you will land back on login.&lt;/p&gt;

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

&lt;p&gt;Inside LoginComponent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;ngOnInit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;securityService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isAuthenticated&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isAuth&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isAuth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&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="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;Now authenticated users never stay on login.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7. Do not manually store tokens
&lt;/h2&gt;

&lt;p&gt;The library already stores tokens in localStorage using DefaultLocalStorageService.&lt;/p&gt;

&lt;p&gt;Do not decode and persist tokens manually.&lt;/p&gt;

&lt;p&gt;If you need profile info:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oidcSecurityService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&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;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;jwtDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sub&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;Keep it clean.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bonus. Lazy loading
&lt;/h2&gt;

&lt;p&gt;You can lazy load the authenticated area:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LayoutComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;canActivate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;AuthorizationGuard&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;loadChildren&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./features/app.routes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APP_ROUTES&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 improves performance and keeps login lightweight.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenges I Faced
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Login appeared to do nothing&lt;br&gt;
Root cause was active SSO session&lt;br&gt;
Fix was prompt equals login&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tokens were returned but Angular did not recognize authentication&lt;br&gt;
Root cause was missing checkAuth on startup&lt;br&gt;
Fix was adding provideAppInitializer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After login it kept returning to login page&lt;br&gt;
Root cause was incorrect guard observable typing&lt;br&gt;
Fix was correcting the guard implementation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Redirect URL landed on login instead of dashboard&lt;br&gt;
Fix was redirecting inside LoginComponent when authenticated&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;Implementing OpenID Connect in Angular 19 without NgModules is actually clean once you understand the lifecycle.&lt;/p&gt;

&lt;p&gt;The key ideas are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always call checkAuth on startup&lt;/li&gt;
&lt;li&gt;Let the library manage tokens&lt;/li&gt;
&lt;li&gt;Keep guards simple&lt;/li&gt;
&lt;li&gt;Understand how redirect URLs affect routing&lt;/li&gt;
&lt;li&gt;Force login prompt only when needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Authentication is less about writing code and more about understanding flow.&lt;/p&gt;

&lt;p&gt;Once you get the flow right, everything becomes predictable.&lt;/p&gt;

&lt;p&gt;Below is a sample Angular app repository.&lt;/p&gt;

&lt;p&gt;👉&lt;a href="https://github.com/Joseph-Ndungi/OpenID-Connect-Authentication.git" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are implementing this on Angular 19 with Node 20 and standalone APIs, this structure should save you a few hours of debugging.&lt;/p&gt;

&lt;p&gt;And probably a bit of frustration too.&lt;/p&gt;

&lt;p&gt;Happy Coding!&lt;br&gt;
👉&lt;a href="https://joseph-ndungi.github.io/josee-writes/" rel="noopener noreferrer"&gt;For More blogs&lt;/a&gt;&lt;/p&gt;

</description>
      <category>angular</category>
      <category>security</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Performance and Scalability</title>
      <dc:creator>Joseph Ndungi</dc:creator>
      <pubDate>Wed, 06 Aug 2025 07:20:22 +0000</pubDate>
      <link>https://dev.to/josephndungi/performance-and-scalability-3koo</link>
      <guid>https://dev.to/josephndungi/performance-and-scalability-3koo</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;From Timeouts to Triumph: A Practical Guide to High-Performance APIs&lt;/strong&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  🚀 Introduction
&lt;/h2&gt;

&lt;p&gt;Recently, our team was working on an API designed to fetch data and generates files at the end of the day. A client requested a large dataset, spanning from August 31st of last year to the current date. To handle this, we shared a script designed to pull the data from over 80 different endpoints.&lt;/p&gt;

&lt;p&gt;When we ran the script, we quickly realized some of the files were not being generated. The reason was clear from the terminal errors: timeouts. The database was getting hammered by an overwhelming number of queries and joins all at once.&lt;/p&gt;

&lt;p&gt;My initial thought was that the client given her size would surely have the resources to handle the load. But we soon understood the real issue: we were hitting the maximum connection limit of the SQL database. It was a classic race condition where too many requests were competing for limited database connections, causing the system to fail. So, what were we supposed to do? This scenario highlights a critical challenge in building scalable systems.&lt;/p&gt;




&lt;h3&gt;
  
  
  🚀 Optimizing APIs for High Throughput
&lt;/h3&gt;

&lt;p&gt;High throughput means handling a large volume of requests quickly and efficiently. When your APIs are slow, the first instinct is often to look at the server's CPU or RAM. But usually, the real bottleneck is how you're accessing your data. In our example, running 80+ queries at once created a traffic jam at the database level.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Key Strategies:&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Write Efficient Database Queries&lt;/strong&gt;: Your database is your foundation. If it's slow, everything is slow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Index Everything&lt;/strong&gt;: The single most important thing you can do. Without an &lt;strong&gt;index&lt;/strong&gt;, your database has to do a "full table scan" to find data, which is like reading an entire book just to find one name. With an index, it's like using the book's index—incredibly fast. Identify the columns you frequently filter or join on (&lt;code&gt;WHERE&lt;/code&gt;, &lt;code&gt;JOIN...ON&lt;/code&gt;) and index them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analyze Your Queries&lt;/strong&gt;: Use tools like &lt;code&gt;EXPLAIN&lt;/code&gt; (in SQL) to see exactly how your database is executing a query. It will show you if you're using indexes correctly and highlight inefficient steps.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Use Asynchronous Operations and Job Queues&lt;/strong&gt;: Not every task needs to happen immediately. For a long-running job like generating historical data files, the API's only responsibility should be to say, "Okay, I've received your request and will work on it."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How it works&lt;/strong&gt;: Instead of processing the request and making the user wait, the API endpoint adds a "job" to a &lt;strong&gt;queue&lt;/strong&gt; (using tools like RabbitMQ, AWS SQS, or Redis). Separate "worker" processes then pick up jobs from this queue one by one and do the heavy lifting: querying the database, processing the data, and generating the file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Benefits&lt;/strong&gt;: This prevents API timeouts, allows you to control concurrency (e.g., only run 5 jobs at a time), and makes your system much more resilient to spikes in load.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h3&gt;
  
  
  📈 Handling Concurrent Requests
&lt;/h3&gt;

&lt;p&gt;The issue of hitting the database's "maximum connection" limit is a classic scalability problem. When hundreds or thousands of requests try to talk to the database at once, the database can't keep up and starts refusing new connections, leading to timeouts and errors.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Key Strategies:&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Implement Connection Pooling&lt;/strong&gt;: This is non-negotiable for any high-performance application. Instead of each request opening a brand new, expensive connection to the database, a &lt;strong&gt;connection pool&lt;/strong&gt; maintains a "pool" of open connections.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How it works&lt;/strong&gt;: A request "borrows" a connection from the pool, uses it for its query, and immediately returns it. This is thousands of times faster and prevents you from exhausting the database's connection limit. Most modern web frameworks and database libraries have this built-in, but you must ensure it's enabled and configured correctly.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Design Bulk Endpoints&lt;/strong&gt;: Instead of having the client call 80+ separate endpoints, design a smarter API. Create a single endpoint that can accept a larger, more complex request.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example&lt;/strong&gt;: Create a &lt;code&gt;POST /api/v1/Clients&lt;/code&gt; endpoint that accepts a JSON payload with a date range (&lt;code&gt;"startDate": "2023-08-31"&lt;/code&gt;, &lt;code&gt;"endDate": "2024-08-04"&lt;/code&gt;). Your backend can then intelligently break this single request into smaller, manageable chunks and feed them to your job queue system without overwhelming anything.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use a Load Balancer&lt;/strong&gt;: You should never run a critical application on a single server. A &lt;strong&gt;load balancer&lt;/strong&gt; is a traffic cop that sits in front of multiple instances of your application. It distributes incoming requests evenly across them, ensuring no single server gets overwhelmed. If one server fails, the load balancer automatically redirects traffic to the healthy ones.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;




&lt;h3&gt;
  
  
  🧠 Caching Strategies That Actually Work
&lt;/h3&gt;

&lt;p&gt;Caching is the art of storing the results of an expensive operation so you don't have to do it again. If users are frequently requesting the same data, hitting your database every single time is incredibly wasteful.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Key Strategies:&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Application-Level Caching&lt;/strong&gt;: This is the simplest form, where you store frequently accessed data in your application's memory. It's fast but limited, as the cache is lost if the app restarts and isn't shared between different server instances.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Distributed Caching with Redis&lt;/strong&gt;: This is the gold standard for backend caching. &lt;strong&gt;Redis&lt;/strong&gt; is an extremely fast, in-memory data store that all your application servers can connect to.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;How it works&lt;/strong&gt;: When a request for data comes in, your application first checks if the result is in Redis.

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cache Hit&lt;/strong&gt;: If it's there, you return the data from Redis immediately without ever touching the database. This is lightning-fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache Miss&lt;/strong&gt;: If it's not there, you query the database, get the result, &lt;strong&gt;store it in Redis for next time&lt;/strong&gt;, and then return it to the user.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;This dramatically reduces the load on your database for read-heavy operations.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Content Delivery Networks (CDNs)&lt;/strong&gt;: A CDN is a network of servers distributed around the globe. It's primarily used to cache "static" assets like images, videos, CSS, and JavaScript files closer to your users, which drastically reduces load times. Some CDNs can also cache API responses, which is perfect for public data that doesn't change often.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;The initial problem of timeouts wasn't a resource failure; it was an &lt;strong&gt;architectural failure&lt;/strong&gt;. The solution wasn't bigger servers, but a smarter approach. By optimizing queries, handling concurrency with queues and connection pools, and leveraging caching, you can build systems that are not only performant but also scalable and resilient. Performance is a feature, and it starts with design.&lt;/p&gt;

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

</description>
    </item>
    <item>
      <title>It works on my machine</title>
      <dc:creator>Joseph Ndungi</dc:creator>
      <pubDate>Wed, 16 Jul 2025 10:18:49 +0000</pubDate>
      <link>https://dev.to/josephndungi/it-works-on-my-machine-1p6a</link>
      <guid>https://dev.to/josephndungi/it-works-on-my-machine-1p6a</guid>
      <description>&lt;h2&gt;
  
  
  A Developer's Guide to Controlled Chaos, 'QA Love', and Sanity
&lt;/h2&gt;




&lt;p&gt;Alright, let's be real. Three years in, and I've uttered "it works on my machine!" with a mix of frustration and genuine confusion for some time. We've all been there, fueled by the "move fast and break things", only to be brought crashing back to reality by the unsung heroes (and occasional villains, depending on your mood) of the software world: Quality Assurance.&lt;/p&gt;

&lt;p&gt;Three years in the industry (so proud of myself), this is my take on how to navigate the wild world of rapid development, embracing QA counterparts, and, most importantly, keeping my sanity intact when the deadlines loom.&lt;/p&gt;

&lt;h3&gt;
  
  
  Move Fast and Break Things (Until QA Calls)
&lt;/h3&gt;

&lt;p&gt;Remember those early days? The thrill of pushing code, seeing it work (mostly), and the belief that speed trumped all? "Move fast and break things," we chanted, picturing ourselves as agile ninjas, fearlessly innovating. And then came the bug reports.&lt;/p&gt;

&lt;p&gt;It usually starts with a polite, "Hey, I found something..." which quickly escalates to a detailed, step-by-step reproduction that makes your blood run cold. You re-read your code, convinced it's flawless, only to realize that, yes, you did forget to handle that edge case where the user tries XYZ. Or maybe you forgot that not everyone has the same super-fast internet connection you do.&lt;/p&gt;

&lt;p&gt;The point is, "move fast and break things" isn't an excuse for carelessness. It's a philosophy born from a desire for iteration, not destruction. The "breaking" should be controlled, intentional, and, ideally, caught &lt;em&gt;before&lt;/em&gt; it hits a client. Think of it like a scientist conducting an experiment: you're pushing boundaries, but you're also recording your findings and anticipating potential side effects.&lt;/p&gt;

&lt;p&gt;This means &lt;strong&gt;unit tests&lt;/strong&gt; aren't just for senior devs; they're your first line of defense. &lt;strong&gt;Code reviews&lt;/strong&gt; aren't just a formality; they're a chance for another pair of eyes to spot your blind spots. And most importantly, &lt;strong&gt;understanding the user flow&lt;/strong&gt; from a non-developer perspective is crucial. Because let's face it, we developers sometimes live in a bubble of perfect conditions and ideal user behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why QA is Actually Your Best Pal
&lt;/h3&gt;

&lt;p&gt;Let's talk about QA. For many of us, especially when we're new and perhaps a little insecure about our code, QA can feel like the enemy. They're the ones finding all the flaws, pointing out your mistakes, and sometimes, making you question your life choices. I've been there, grumbling under my breath about "bug reports" and "edge cases that no one would ever do."&lt;/p&gt;

&lt;p&gt;But here's the honest truth: &lt;strong&gt;QA is the devil's advocate, and that's a &lt;em&gt;good thing&lt;/em&gt;.&lt;/strong&gt; They are literally paid to try and break your software. Their job is to look at your beautiful, carefully crafted code and imagine every twisted, illogical, and downright bizarre way a human might interact with it. And trust me, humans are &lt;em&gt;very&lt;/em&gt; good at being bizarre.&lt;/p&gt;

&lt;p&gt;Think of it this way: every bug QA finds is a bug that a client &lt;em&gt;doesn't&lt;/em&gt;. Every frustrating back-and-forth over a bug report is a lesson learned, a better understanding of edge cases, and ultimately, a more robust product.&lt;/p&gt;

&lt;p&gt;So, how do we stop fearing them and start appreciating them?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Communicate, communicate, communicate:&lt;/strong&gt; Don't just toss your code over the fence and wait for the inevitable. Talk to your QA team early in the development cycle. Understand their testing plans, share your assumptions, and ask for their input. They might even spot potential issues before you write a single line of code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provide clear context:&lt;/strong&gt; When you submit a feature or a bug fix, don't just say "done." Explain what you've done, what you've tested, and any known limitations. The more information they have, the more efficiently they can test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embrace the feedback:&lt;/strong&gt; It's not personal. It's about the software. When they find a bug, try to understand &lt;em&gt;why&lt;/em&gt; it's a bug from their perspective. Ask questions. Learn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show appreciation:&lt;/strong&gt; A simple "thanks for catching that" goes a long way. They're doing critical work, often under pressure, to ensure your hard work shines.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building a strong partnership with QA isn't just about making your life easier; it's about delivering better software. They are your allies in the quest for quality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Underpromise, Overdeliver (and Keep Your Sanity)
&lt;/h3&gt;

&lt;p&gt;Every developer knows the feeling of being asked, "How long will that take?" and instantly feeling a cold sweat. Stakeholders want answers, and they want them fast. The pressure to promise the moon can be immense.&lt;/p&gt;

&lt;p&gt;But here's the secret sauce, the developer's survival strategy: &lt;strong&gt;underpromise, overdeliver.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This isn't about being lazy or intentionally slow. It's about being realistic, building in buffer time, and managing expectations. Here's how it plays out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pad your estimates:&lt;/strong&gt; When you think something will take 2 days, say 3. When you think it'll take a week, say a week and a half. Why? Because things &lt;em&gt;always&lt;/em&gt; come up. Unexpected bugs, new requirements, environment issues, that one meeting that drags on for two hours – life happens. That extra buffer isn't wasted time; it's sanity insurance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Break down tasks:&lt;/strong&gt; Don't estimate a monolithic "build the whole feature" task. Break it down into smaller, manageable chunks. This makes your estimates more accurate and allows you to track progress more effectively. "Develop API endpoint" is easier to estimate than "build social media integration."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Communicate scope changes:&lt;/strong&gt; This is huge. If a stakeholder suddenly asks for a "small" addition that you know will add significant time, speak up immediately. Don't let scope creep silently derail your timelines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manage expectations:&lt;/strong&gt; If you hit a roadblock, communicate it early. Don't wait until the day before the deadline to announce a delay. Proactive communication builds trust, even when things go awry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Celebrate the "overdelivery":&lt;/strong&gt; When you estimate 3 days and finish in 2, you're a hero. It feels great, and it builds confidence with your stakeholders. This creates a positive feedback loop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This strategy isn't just about hitting deadlines; it's about reducing stress, avoiding burnout, and maintaining a healthy work-life balance. When you consistently deliver what you promise (or even a little more), you build a reputation for reliability. That reputation is worth its weight in gold.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Human Element in the Machine
&lt;/h3&gt;

&lt;p&gt;So, there you have it. Three years in, and I've learned that being a good developer isn't just about writing elegant code. It's about understanding the chaotic dance of rapid development, forging strong alliances with your QA team, and mastering the art of managing expectations.&lt;/p&gt;

&lt;p&gt;It's about controlled chaos, not careless breaking. It's about seeing QA as your partner, not your enemy. And it's about being honest with yourself and others about what's achievable, ensuring that when you do deliver, you do it with a sense of accomplishment, not exhaustion.&lt;/p&gt;

&lt;p&gt;Keep coding, keep learning, and remember: we're all just trying to build cool stuff without completely losing our minds.&lt;/p&gt;

&lt;p&gt;What's your biggest "it works on my machine!" moment?&lt;/p&gt;

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

</description>
    </item>
    <item>
      <title>Navigating Kenyan Roads on Two Wheels</title>
      <dc:creator>Joseph Ndungi</dc:creator>
      <pubDate>Mon, 09 Jun 2025 17:46:40 +0000</pubDate>
      <link>https://dev.to/josephndungi/navigating-kenyan-roads-on-two-wheels-127c</link>
      <guid>https://dev.to/josephndungi/navigating-kenyan-roads-on-two-wheels-127c</guid>
      <description>&lt;h3&gt;
  
  
  &lt;strong&gt;Answering the Big Question: Is It Safe to Cycle on Kenyan Roads?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The other day, as I proudly showed off my new bicycle, a friend looked at me with the kind of genuine concern, leaned in and asked the question that’s on every aspiring Kenyan cyclist's mind: &lt;em&gt;“Lakini, is it even safe to cycle on our roads?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It’s a fair question. Let’s be honest: Kenyan roads are a special kind of ecosystem. It’s a concrete jungle where matatus frequently challenge the laws of physics, boda bodas materialize out of thin air, and some potholes have their own postal codes. The idea of navigating this beautiful chaos on a metal frame with two skinny wheels can seem, well, insane.&lt;/p&gt;

&lt;p&gt;And yet, my answer, after a few hundred kilometres clocked and a thick layer of dust permanently attached to my soul, is a cautious but resounding &lt;strong&gt;YES&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It can be safe. It can be incredibly rewarding. It can be the best thing you do for your body and mind. But it’s not about luck. It's about being smart, being prepared, and being more visible than a traffic police officer on a Friday afternoon. Here’s how I learned to stop worrying and love the ride.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Section 1: The Golden Rules of Staying Safe&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Think of this as your survival guide. It’s less about having the strongest legs and more about having the sharpest wits.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Be a Human Traffic Light (Be Hyper-Visible)&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Your number one job on the road is to make sure everyone can see you. You need to be impossible to ignore.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dress Loud and Proud:&lt;/strong&gt; This is not the time for your cool, all-black aesthetic. Think neon. Hi-vis yellow, fluorescent green, blinding orange. You want to look like a highlighter pen that’s decided to take up a sport.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lights, Camera, Action!:&lt;/strong&gt; Lights are not just for the night. A flashing white front light and a red rear light are your best friends, even in the bright midday sun. Modern daytime flashlights are designed to be seen from over a kilometre away. Look for something with at least 100 lumens for the rear and 200+ lumens for the front. They cut through the visual noise and scream “I AM HERE!” to distracted drivers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Master the "Matatu &amp;amp; Boda Boda Dance"&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;This is a delicate art form that requires observation, prediction, and a healthy dose of pessimism.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The First Commandment: Assume You Are Invisible.&lt;/strong&gt; Seriously. Engrave this on your helmet. Never, ever assume a driver, rider, or pedestrian has seen you. Always ride as if you’re a ghost they can drive right through. This mindset keeps you sharp.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be Boringly Predictable:&lt;/strong&gt; Sudden moves are for blockbuster movies, not for your daily commute. Ride in a straight, predictable line. Use clear hand signals &lt;em&gt;before&lt;/em&gt; you turn or stop. Want to move out to avoid a pothole? Check your shoulder, signal, and then move. No surprises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claim Your Lane (When Safe):&lt;/strong&gt; It feels counterintuitive, but hugging the far edge of the road can be dangerous. It invites drivers to squeeze past you with only inches to spare. Instead, ride about a meter from the curb. This makes you more visible, forces cars to give you a proper berth when overtaking, and gives you an escape route if you need to dodge a sudden obstacle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Power of the Gaze:&lt;/strong&gt; At junctions and roundabouts, try to make eye contact with drivers. A simple nod or glance is often all it takes to confirm they’ve registered your existence. It’s a silent, two-wheeled conversation that can save your skin.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Choose Your Battlefield (Route and Timing)&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;You wouldn't learn to swim in the middle of the ocean, so don't learn to cycle on Mombasa Road at 5 PM.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Beginner Havens:&lt;/strong&gt; Start somewhere peaceful. Explore the quiet roads in residential estates on a Sunday morning. Better yet, head to controlled environments. &lt;strong&gt;Karura Forest&lt;/strong&gt; offers beautiful, car-free trails perfect for building confidence. The climb up &lt;strong&gt;Ngong Hills&lt;/strong&gt; is tough, but the views and relatively low traffic are worth it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Magic Hour:&lt;/strong&gt; The secret to blissful Kenyan cycling is the time of day. Set your alarm for 5:30 AM. Riding between then and 7:30 AM is a different world. The air is cool and crisp, the sun is rising, and the roads are wonderfully empty. You get your exercise in before the chaos even wakes up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Section 2: Gearing Up (The Tech and The Tin)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Cycling can look like an expensive hobby, but you can get started without selling a kidney.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your First Trusty Steed:&lt;/strong&gt; You do not need a KES 200,000 carbon fibre race bike that weighs less than your lunch. For Kenyan roads, a sturdy &lt;strong&gt;Mountain Bike (MTB)&lt;/strong&gt; or a versatile &lt;strong&gt;Hybrid Bike&lt;/strong&gt; is your best bet. Their tougher frames and wider tires are perfect for handling our… &lt;em&gt;character-filled&lt;/em&gt; road surfaces. Good quality second-hand bikes from reputable dealers are a fantastic way to start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Non-Negotiables:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A Helmet:&lt;/strong&gt; If you have a head, you need a helmet. Full stop. Modern helmets are light, well-ventilated, and some even have tech like MIPS (Multi-directional Impact Protection System) for extra safety.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lights:&lt;/strong&gt; Yes, them again. They are that important.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Water Bottle &amp;amp; Cage:&lt;/strong&gt; You will sweat. A lot. Hydration is critical.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;The "My Life is So Much Better Now" Gear:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Padded Cycling Shorts (Bibs):&lt;/strong&gt; Let’s talk about your behind. After 10km on a bike seat, it will start complaining. After 20km, it will file a formal grievance. Padded shorts are the single greatest invention for cycling comfort.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gloves:&lt;/strong&gt; They absorb vibrations, protect your hands if you fall, and improve your grip when things get sweaty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A Small Pump &amp;amp; Puncture Repair Kit:&lt;/strong&gt; Getting a flat tyre is a rite of passage. Learning to fix it on the side of the road makes you feel like a self-sufficient genius.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Section 3: The Incredible Rewards (Why We Do This)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;So you’ve dodged the potholes and bought the gear. Why is it all worth it?&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;The Unwind Button&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;As a tech enthusiast, I spend my days staring at screens. Cycling has become my analogue escape, my ultimate unwind button. It’s a space where the only notifications are the changing gears and my own heartbeat. I started tracking my rides on &lt;strong&gt;Strava&lt;/strong&gt;, a social network for athletes. At first, it was just to see how far I’d gone. But it quickly became a fascinating data stream of my own progress.&lt;/p&gt;

&lt;p&gt;Seeing my average speed increase from 15 km/h to 20 km/h, watching my elevation gain stack up week after week, and conquering a hill that left me breathless a month ago—it's incredibly motivating. It gamifies fitness. If you want to see the stats and follow my journey, you can find me on Strava under the username &lt;strong&gt;Joseph Ndungi&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Discover a Hidden Kenya&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;From a car, you see Kenya. On a bike, you &lt;em&gt;experience&lt;/em&gt; it. You smell the rain hitting the tarmac in Limuru’s tea fields. You feel the sun on your back as you coast down the hills. You hear the cheerful "Niaje!" from kids in a small village. You discover tiny cafes and viewpoints you’d never notice while stuck in traffic. It’s a sensory overload in the best possible way.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Finding Your Tribe&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;The best part? You don't have to do it alone. The cycling community in Kenya is booming, friendly, and diverse. Riding in a group is not only safer but also way more fun. Most groups organize weekly/monthly rides to advocate for cycling in the city, and clubs have rides for all skill levels. The camaraderie of a pre-ride coffee and a post-ride discussion about who conquered which hill segment on Strava is half the fun.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion: Your Invitation to the Road&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;So, back to that big question: Is it safe to cycle on Kenyan roads?&lt;/p&gt;

&lt;p&gt;The road itself is neutral. It's how you approach it that matters. With the right mindset (assume you're invisible), the right gear (be a traffic light), and the right company, it is not only safe, but it's also one of the most life-affirming, stress-busting, and adventurous hobbies you can pick up in this country.&lt;/p&gt;

&lt;p&gt;So, start small. Borrow a bike and ride around Karura this weekend. Look up a beginner group ride. Just start. The road is waiting, and it's more beautiful than you can imagine.&lt;/p&gt;

&lt;p&gt;Happy &lt;del&gt;Coding&lt;/del&gt; Cycling!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Tradeoffs Between In-Memory Checks and Database Queries</title>
      <dc:creator>Joseph Ndungi</dc:creator>
      <pubDate>Tue, 20 May 2025 12:19:52 +0000</pubDate>
      <link>https://dev.to/josephndungi/tradeoffs-between-in-memory-checks-and-database-queries-4egc</link>
      <guid>https://dev.to/josephndungi/tradeoffs-between-in-memory-checks-and-database-queries-4egc</guid>
      <description>&lt;h1&gt;
  
  
  To Cache or Not to Cache: Tradeoffs Between In-Memory Checks and Database Queries
&lt;/h1&gt;

&lt;p&gt;In software engineering, many real-world problems boil down to tradeoffs, and one recurring decision is whether to &lt;strong&gt;query the database multiple times or fetch once and operate on data in memory&lt;/strong&gt;. This seemingly small architectural choice can shape performance, reliability, and maintainability in ways that are easy to overlook but matter deeply in production.&lt;/p&gt;

&lt;p&gt;One of the most fundamental and frequently encountered tradeoffs revolves around data access: when faced with needing multiple pieces of information about a single entity (like a user or an order), should we fetch everything once and perform checks in our application's memory, or should we make several targeted queries to the database, asking for specific answers each time?&lt;/p&gt;

&lt;p&gt;This isn't just an academic exercise. It's a decision that impacts user experience, system stability, and development velocity. It surfaces during critical business operations: activating a new user, processing a customer order, validating complex state transitions in a workflow, or authorizing access based on multiple criteria. Get it wrong, and you might build a system that's sluggish, brittle, or scales poorly. Get it right, and you pave the way for a responsive, robust, and maintainable application.&lt;/p&gt;

&lt;p&gt;Consider a common scenario: activating a user account. The business rules might be simple: "Activate the user only if they exist, they are not currently frozen, and they are not already active." This seemingly straightforward task immediately presents two primary implementation paths, each with profound implications. Do we load the entire user record – ID, status, flags, maybe even recent activity – into our application's memory and then run our checks? Or do we ask the database three specific questions: "Does user X exist?", "Is user X frozen?", "Is user X active?"&lt;/p&gt;

&lt;p&gt;Recently, while working on a user activation/deactivation feature, my team encountered this dilemma. It sparked a healthy debate, reminding us that even familiar problems deserve fresh consideration based on the current system context, expected load, and evolving architecture.&lt;/p&gt;

&lt;p&gt;This article delves into the engineering tradeoffs between these two fundamental approaches. We'll explore the nuances of performance, the subtleties of data consistency, the impact on code clarity, and the long-term effects on scalability, helping you make more informed decisions in your projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Two Approaches
&lt;/h2&gt;

&lt;p&gt;Let’s establish the two primary paths we considered. They apply not just to user activation, but to many workflows: order validations, access checks, billing logic, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Single Query, In-Memory Validation
&lt;/h3&gt;

&lt;p&gt;This approach front-loads the data fetch. You query once—ideally in a single row fetch or join—and get everything you might need about the entity (e.g., a user). Then you perform all logical checks using in-memory operations.&lt;/p&gt;

&lt;p&gt;This is appealing for its simplicity. All checks are co-located, there’s just one database hit, and your business logic can proceed without waiting for more network calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Multiple Targeted Queries or Stored Procedures
&lt;/h3&gt;

&lt;p&gt;Here, you break the logic down. You might ask, “Does this user exist?”—issue a quick query. Then, “Are they frozen?”—issue another. Each check is independent, minimal, and potentially reusable across the codebase or services. Often, this is done via helper methods or stored procedures that encapsulate business logic within the database.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Tradeoffs and Considerations
&lt;/h2&gt;

&lt;p&gt;So, which is better? As with most design decisions, it depends. Let’s explore the dimensions that influence the choice.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Performance
&lt;/h3&gt;

&lt;p&gt;Performance is often the most immediate concern. Each database call has inherent latency, especially in distributed systems or cloud-hosted architectures.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;In-Memory Wins:&lt;/strong&gt; Every database query incurs latency – the time it takes for the request to travel over the network to the database, be processed, and for the response to travel back. Even on fast networks, this "round-trip time" (RTT) adds up. Fetching once drastically reduces this overhead. In cloud environments, especially across availability zones or regions, network latency can be significant, making single fetches markedly faster for the end-user or calling service. Think of it as making one phone call versus three separate calls – the setup time for each call adds up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Queries Can Shine When:&lt;/strong&gt; Each query is light, well-indexed, and operates on narrowly scoped data. This is especially true when you don’t need the full object and can get away with querying just what you need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network Bandwidth:&lt;/strong&gt; Option 1 might transfer more data in total if the user object is large and you only need small parts of it. Option 2 transfers minimal data per query, potentially saving bandwidth if the network is constrained, but increases the number of network packets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, remember that &lt;em&gt;latency is multiplicative&lt;/em&gt;, not additive. Two 10ms queries in serial cost more than one 15ms query. The cost of multiple calls adds up, especially in high-throughput systems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;If your system operates over a high-latency network, in-memory is often the safer bet.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  2. Readability and Maintainability
&lt;/h3&gt;

&lt;p&gt;Code that’s easy to understand is easier to extend and debug. That’s not just a nicety—it’s critical for long-term maintainability.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single Fetch Flow:&lt;/strong&gt; This makes it easier to read through a function and understand the full validation logic. Everything you need is right there, in one place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modular Checks:&lt;/strong&gt; Breaking out logic into discrete, self-contained checks (e.g., &lt;code&gt;isUserFrozen(userId)&lt;/code&gt;) can lead to better separation of concerns and reusability. This is useful if validations are reused in multiple flows or services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cognitive Load:&lt;/strong&gt; For simple cases, the inline checks (Option 1) might be easier to grasp immediately. For complex validation logic involving multiple conditions, breaking it down into well-named modular functions (Option 2) can make the overall process easier to understand and test, even if it requires navigating between different functions or files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing:&lt;/strong&gt; Unit testing the logic of Option 1 is simple once the object is mocked. Integration testing requires a database connection. Option 2 lends itself well to unit testing each check function individually (if they are in the application layer) or requires integration testing if relying heavily on database procedures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On large teams or in domain-heavy environments, modular checks can also reflect business rules more clearly and be more testable.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;There’s a tradeoff between linear simplicity and modular clarity. Pick what fits your team and codebase style.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  3. Scalability and System Load
&lt;/h3&gt;

&lt;p&gt;Under light usage, either approach may be fine. But under heavy load, the wrong choice can put significant pressure on either your database or your application servers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fewer DB Calls = Less Load:&lt;/strong&gt; If thousands of requests per minute all make 3–5 queries each, your database will feel it. One query per request can significantly reduce load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory Footprint Matters:&lt;/strong&gt; Fetching large objects into memory, especially with relations, joins, or blobs, can increase pressure on your application memory, garbage collector, or runtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is often a balancing act. In-memory operations reduce I/O but increase RAM usage. If you’re memory-constrained or running in a serverless environment, you might prefer lean, focused queries.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Think about which resource is your bottleneck—CPU, memory, or database—and design accordingly.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  4. Data Freshness
&lt;/h3&gt;

&lt;p&gt;This one is subtle but crucial in systems with concurrency.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stale Reads (The In-Memory Risk):&lt;/strong&gt; In Option 1, you fetch the user data at the beginning of the operation. If, between the time you fetched the data and the time you perform a check (e.g., user.isActive()), another concurrent process modifies the user's state in the database (e.g., an administrator activates them), your in-memory object is now stale. Your check will be based on outdated information, potentially leading to incorrect decisions or errors (like trying to activate an already active user).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time Accuracy (The Multiple Query Benefit):&lt;/strong&gt; Each targeted query in Option 2 goes directly to the database at the moment the check is needed. This significantly reduces the window for stale data. The result of isUserActive(userId) reflects the state in the database at right then. This is crucial in systems with high concurrency where data changes frequently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Race Conditions:&lt;/strong&gt; This highlights the potential for race conditions. Imagine two requests trying to activate the same inactive user simultaneously. With Option 1, both might fetch the inactive user, pass the in-memory check, and then both attempt the activation database update. With Option 2, the second request's isUserActive check is more likely to see that the first request has already completed the activation (depending on transaction isolation).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In systems with high concurrency or strict consistency requirements, in-memory checks can create race conditions unless mitigated with transactions or optimistic locking.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Favor targeted queries when freshness matters more than performance.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  5. Atomicity and Transactions
&lt;/h3&gt;

&lt;p&gt;Much of this debate becomes moot inside a well-structured transaction.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transaction Scope:&lt;/strong&gt; If you're wrapping everything in a transaction, a single read with in-memory checks keeps things consistent and simple. You lock the state at the beginning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Queries Need Coordination:&lt;/strong&gt; Without a transaction, issuing separate queries can lead to time-of-check to time-of-use (TOCTOU) issues—i.e., you checked the state, but it's changed before you acted on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s why transaction boundaries are vital. Use them if you need to validate and mutate in one go.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;If you're not using transactions, the risk of inconsistent reads increases with each separate query.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  When to Use Each Approach
&lt;/h2&gt;

&lt;p&gt;These guidelines can help you decide based on your context:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✔ Use In-Memory When
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You are performing multiple checks on different attributes of the same entity within a single request or function scope.&lt;/li&gt;
&lt;li&gt;You would likely need to fetch most of the entity's data eventually anyway for the main operation.&lt;/li&gt;
&lt;li&gt;Minimizing database round-trips and network latency is a primary performance concern (e.g., high-traffic API, cloud environment).&lt;/li&gt;
&lt;li&gt;The operations need to be atomic, and you can comfortably wrap the entire fetch-check-update sequence in a single database transaction.&lt;/li&gt;
&lt;li&gt;The risk of the data becoming stale between the initial fetch and the checks is acceptably low for the use case, or mitigated by transactional locking.&lt;/li&gt;
&lt;li&gt;The memory footprint of the fetched object is manageable for your application servers under expected load.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✔ Use Multiple Queries or Stored Procedures When
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The validation logic is complex and highly reusable across different parts of the application or even different services (promoting DRY).&lt;/li&gt;
&lt;li&gt;Data freshness is paramount, and the risk of acting on stale data read moments earlier is unacceptable due to high concurrency.&lt;/li&gt;
&lt;li&gt;Individual checks are significantly cheaper (e.g., hitting perfect indexes for boolean flags) than fetching the entire object, and network latency is low.&lt;/li&gt;
&lt;li&gt;You need to enforce strict data access patterns or security at the database level, often achieved via specific stored procedures that only expose necessary checks/actions.&lt;/li&gt;
&lt;li&gt;Application server memory is extremely constrained, making loading potentially large objects undesirable.&lt;/li&gt;
&lt;li&gt;You are operating in a distributed system where ensuring consistency across multiple steps requires careful orchestration, and individual checks are natural integration points.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Software engineering is a series of tradeoffs, and this one is prevalent yet easy to underestimate. As with caching, the allure of “faster” or “cleaner” must be balanced against consistency, observability, and correctness.&lt;/p&gt;

&lt;p&gt;In some cases, a hybrid approach works best: one main fetch with a few “fresh” spot checks for sensitive attributes. In others, the business rules themselves may dictate the approach, particularly in systems with strict audit, concurrency, or compliance requirements.&lt;/p&gt;

&lt;p&gt;Ultimately, a well-reasoned tradeoff, documented and understood by the team, is far more valuable than rigidly adhering to a set of principles. Understand the forces at play – performance, consistency, maintainability, scalability – and choose the path that best serves your application's needs, both today and in the foreseeable future.&lt;/p&gt;

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

</description>
    </item>
    <item>
      <title>The Pressure is Real</title>
      <dc:creator>Joseph Ndungi</dc:creator>
      <pubDate>Fri, 09 May 2025 06:10:07 +0000</pubDate>
      <link>https://dev.to/josephndungi/the-pressure-is-real-1m91</link>
      <guid>https://dev.to/josephndungi/the-pressure-is-real-1m91</guid>
      <description>&lt;h1&gt;
  
  
  From Junior to Senior: Navigating the Path in Software Engineering
&lt;/h1&gt;

&lt;p&gt;When you land your first role as a software engineer, you're full of excitement and maybe a bit of impostor syndrome. You’ve passed coding interviews, built a few side projects, and even perhaps shipped some production code. But then the reality hits: transitioning from a junior developer to a senior one is less about typing faster or writing the most elegant one-liner, and more about communication, ownership, and soft skills that no tutorial ever quite teaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Pressure is Real—But So is the Growth
&lt;/h2&gt;

&lt;p&gt;Junior developers often face a unique kind of pressure. You’re expected to learn quickly, ship code reliably, and adapt to a team dynamic you didn’t encounter in school or bootcamp. Deadlines loom. Bugs sneak in. Code reviews are humbling (if not outright bruising). You’re often trying to figure out not just &lt;em&gt;how&lt;/em&gt; to do something, but why it’s done that way.&lt;/p&gt;

&lt;p&gt;The good news? Every developer—yes, even the 10x engineer you think has it all figured out—started here. The journey from junior to senior isn't about avoiding mistakes. It’s about learning from them.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Communication: The True Superpower
&lt;/h2&gt;

&lt;p&gt;Ask any senior engineer what skill they value the most in others, and chances are high they’ll say communication. Writing code is one part of the job; explaining what it does, why it matters, and how it fits into a broader system is another.&lt;/p&gt;

&lt;p&gt;As a junior, you may hesitate to speak up in meetings or on Teams. Over time, you realize that good communication doesn’t mean having all the answers—it means asking good questions, writing clear documentation, updating projects and QA, and being transparent about blockers. It also means learning to give and receive feedback constructively.&lt;/p&gt;

&lt;p&gt;Even within dev-to-dev interactions, communication shows up unexpectedly: your Git commit messages, the clarity of your variable names. It's all part of the engineering conversation.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Prompt Engineering: A New Literacy
&lt;/h2&gt;

&lt;p&gt;In today’s AI-powered dev world, prompt engineering is becoming a surprisingly valuable skill. Knowing how to interact with AI tools like ChatGPT or GitHub Copilot is a productivity boost.&lt;/p&gt;

&lt;p&gt;For juniors, the key is in learning how to ask for what you need. That means writing specific, detailed prompts. Instead of “Help me fix this bug,” try “Here’s the error I’m seeing in my Angular app when I load FullCalendar with custom options…”&lt;/p&gt;

&lt;p&gt;Prompt engineering is just communication with a machine. And like all communication, it improves with empathy, precision, and practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Technical Interview Prep: Algorithms vs. System Design
&lt;/h2&gt;

&lt;p&gt;When you’re early in your career, interview prep often centers around LeetCode-style algorithm questions. And yes, they matter—especially if you’re applying to FAANG or similar companies.&lt;/p&gt;

&lt;p&gt;But as you move toward senior roles, interviews change. You’ll be asked to explain trade-offs in architecture, design scalable systems, or debug a failing integration with limited information. This is where system design interviews come in.&lt;/p&gt;

&lt;p&gt;Here’s the truth: Senior engineers don’t always write more code—they design better systems. They think in terms of APIs, scalability, observability, failure tolerance, and developer experience. If algorithms are the grammar of engineering, system design is the storytelling.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Soft Skills Are Not Optional
&lt;/h2&gt;

&lt;p&gt;There’s a myth that software engineering is all about hard skills—algorithms, syntax, performance tuning. But what separates a senior from a junior is often the &lt;em&gt;soft stuff&lt;/em&gt;: mentorship, stakeholder management, conflict resolution, empathy.&lt;/p&gt;

&lt;p&gt;Senior engineers often act as glue for their teams. They bring people together, prevent miscommunication, and create psychological safety. They help onboard juniors, manage up to product managers, and influence architectural decisions—not just by authority, but by trust.&lt;/p&gt;

&lt;p&gt;If you're the kind of person who listens actively, stays calm during outages, and explains trade-offs instead of just pushing an opinion, you’re already practicing senior behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Freelancing vs. Full-Time: Two Roads to Growth
&lt;/h2&gt;

&lt;p&gt;The rise of freelancing platforms and remote-first companies has made software engineering more flexible than ever. So what’s better—freelancing or full-time?&lt;/p&gt;

&lt;p&gt;There’s no right answer. Freelancing can offer variety, autonomy, and higher pay per hour. But it also demands strong self-management, marketing, and client communication skills. You're your own tech lead, PM, and support team.&lt;/p&gt;

&lt;p&gt;Full-time roles offer stability, mentorship, and a structured career ladder. They’re especially valuable for juniors who want to grow under the guidance of experienced teammates and develop broader systems thinking.&lt;/p&gt;

&lt;p&gt;In truth, many devs try both across their careers. The key is understanding what kind of growth you need right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Mentorship &amp;amp; Paying It Forward
&lt;/h2&gt;

&lt;p&gt;One of the most underrated parts of seniority is mentorship. At some point, you’ll be the one reviewing code, explaining the architecture, or helping someone debug a gnarly issue you’ve seen before.&lt;/p&gt;

&lt;p&gt;Mentorship isn’t just about teaching—it’s about learning to explain clearly, listen patiently, and see things from someone else’s level of experience. It makes &lt;em&gt;you&lt;/em&gt; better, too.&lt;/p&gt;

&lt;p&gt;Juniors who ask questions, document what they learn, and share knowledge early on are already setting the foundation for mentorship.&lt;/p&gt;




&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;The path from junior to senior developer isn’t linear. It’s a winding road full of messy PRs, awkward retros, unexpected breakthroughs, and moments when you feel like you finally get it—only to hit a new challenge that humbles you all over again.&lt;/p&gt;

&lt;p&gt;Along the way, many junior developers fall into the trap of trying to be a jack of all trades—dabbling in frontend, backend, DevOps, AI, design, you name it. While it’s great to explore and stay curious, spreading yourself too thin can leave you feeling overwhelmed and underdeveloped in any one area. Depth, not just breadth, is what builds trust and leads to meaningful contributions.&lt;/p&gt;

&lt;p&gt;And here’s something many of us learn the hard way: you don’t have to figure it all out alone. Seeking mentorship is not a weakness—it’s a strength. Learning from those who’ve already walked the path can save you months of frustration, help you avoid common pitfalls, and accelerate your growth in ways books and courses can’t. Whether it’s a senior on your team, a community mentor, or even a peer who’s just one step ahead, reach out, ask questions, and stay open.&lt;/p&gt;

&lt;p&gt;Ultimately, becoming a senior engineer isn’t about having all the answers. It’s about learning how to ask the right questions, communicate clearly, grow intentionally, and lift others as you rise.&lt;/p&gt;

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

</description>
    </item>
    <item>
      <title>Web performance</title>
      <dc:creator>Joseph Ndungi</dc:creator>
      <pubDate>Tue, 23 Jan 2024 06:18:53 +0000</pubDate>
      <link>https://dev.to/josephndungi/web-performance-2pd3</link>
      <guid>https://dev.to/josephndungi/web-performance-2pd3</guid>
      <description>&lt;h3&gt;
  
  
  Web performance is all about making websites fast, including making slow processes seem fast
&lt;/h3&gt;

&lt;p&gt;In essence, web performance is about both the measurable aspects and the user's perceived experience when interacting with a site or app&lt;/p&gt;

&lt;h3&gt;
  
  
  Key areas include
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reducing Load Time&lt;/strong&gt;: This involves minimizing the size and number of files needed to render a website, and using techniques like preloading to speed up the process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usability from the Start&lt;/strong&gt;: We focus on loading website assets efficiently, enabling users to start interacting with the site quickly. Techniques like lazy loading enhance this experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Smoothness and Interactivity&lt;/strong&gt;: A seamless user experience is crucial. We explore best practices in creating smooth animations and responsive interfaces.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Perceived Performance&lt;/strong&gt;: It's not just about how fast a website loads, but how fast it feels to the user. Strategies like engaging loading screens can significantly improve user perception.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance Measurements&lt;/strong&gt;: We delve into various metrics and tools for measuring and optimizing web performance, ensuring that the site not only performs well but continues to do so over time.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Maintaining optimal performance is relatively straightforward for basic websites. However, the task becomes more challenging with intricate web applications, where delivering a consistently smooth user experience is crucial.&lt;/p&gt;

&lt;p&gt;The problem that exists is that while our website may behave as expected on our devices, our users won’t be accessing the device from our devices, but their own and under different circumstances.&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%2Fhackernoon.imgix.net%2Fhn-images%2F1%2AookfwogTLx_1qhHaiFJoJw.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%2Fhackernoon.imgix.net%2Fhn-images%2F1%2AookfwogTLx_1qhHaiFJoJw.png" title="Your machine isn’t the real world" alt="It works on my machine" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In light of this, I have put together a selection of key metrics that are essential for evaluating the performance of websites&lt;/p&gt;

&lt;h4&gt;
  
  
  1. First Contentful Paint (FCP)
&lt;/h4&gt;

&lt;p&gt;This performance metric, known as First Contentful Paint (FCP), gauges the time taken for the initial piece of content on a website - be it text, images, or other elements - to appear on the user's screen.&lt;/p&gt;

&lt;p&gt;FCP measures how long it takes the browser to render the first piece of DOM content after a user navigates to your page. Images, non-white canvas elements, and SVGs on your page are considered DOM content; anything inside an iframe isn't included.&lt;/p&gt;

&lt;p&gt;FCP plays a significant role in shaping a user's perception of the page's loading speed, making its optimization vital.&lt;/p&gt;

&lt;p&gt;It's important to aim for an FCP of 1.8 seconds or less for client websites to ensure optimal performance.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. First Meaningful Paint (FMP)
&lt;/h4&gt;

&lt;p&gt;Sounds pretty similar to First Contentful Paint (FCP), right? There’s a key difference.&lt;/p&gt;

&lt;p&gt;As the name suggests, FMP is how long it takes for the first meaningful content to display (i.e., whatever users actually want to view).&lt;/p&gt;

&lt;p&gt;This value is often the same as First Contentful Paint (FCP) but varies based on your client’s website.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Largest Contentful Paint (LCP)
&lt;/h4&gt;

&lt;p&gt;This perceived performance metric is the time it takes for the largest piece of content to load on your client’s website (e.g., a video embed, image slideshow, text body).&lt;/p&gt;

&lt;p&gt;It’s worth noting that LCP falls under Google’s Core Web Vitals, which means it directly impacts a website’s performance on Google search results.&lt;/p&gt;

&lt;p&gt;It makes sense when you think about it. LCP is useful for a website visitor to gauge how long they’ll have to wait to access your client’s full website (which often determines whether they’ll stay or not).&lt;/p&gt;

&lt;p&gt;When you measure LCP either using Lighthouse or PageSpeed Insight, it usually provides a list of reasons why your LCP was given a certain score and how you can optimize for it.&lt;br&gt;
&lt;strong&gt;Some of the common things you can do to optimize for LCP.&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Optimize Server Response Time&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To speed up your website, use a Content Delivery Network (CDN) for your images, CSS, JavaScript, HTML, and static files. This helps with caching and improves a metric known as Time To First Byte (TTFB), which is the time it takes to receive the first byte of data from the server. TTFB affects key performance metrics like Largest Contentful Paint (LCP) and First Contentful Paint (FCP).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Enhance Resource Discovery&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Load critical resources affecting LCP as soon as possible. Browsers have a preload scanner that prefetches items like images and stylesheets marked with &lt;code&gt;rel=preload&lt;/code&gt;. Ensure your images' URLs are in the &lt;code&gt;srcset&lt;/code&gt; or &lt;code&gt;src&lt;/code&gt; attributes, and preload CSS background images and fonts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Prioritize Key Resources&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Ensure important resources load first. Use the &lt;code&gt;fetchpriority="high"&lt;/code&gt; attribute for essential images. De-prioritize less critical resources to conserve bandwidth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Other Optimizations&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switch to modern image formats like WebP, ideally using an image CDN for format compatibility.&lt;/li&gt;
&lt;li&gt;Implement responsive images to suit different screen sizes.&lt;/li&gt;
&lt;li&gt;Compress images to reduce size without losing quality.&lt;/li&gt;
&lt;li&gt;Set appropriate cache settings to minimize repeat load times.&lt;/li&gt;
&lt;li&gt;Eliminate unnecessary CSS and JavaScript.&lt;/li&gt;
&lt;li&gt;Optimize font sizes for efficiency.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  4. Cumulative Layout Shift (CLS)
&lt;/h4&gt;

&lt;p&gt;Similar to Largest Contentful Paint, CLS also falls in the Google Core Web Vitals bucket.&lt;br&gt;
Users often find it annoying when content they are reading suddenly shifts to a different spot on the page. This can happen due to images loading and pushing down existing content or dynamic additions like ads that force content to move.&lt;/p&gt;

&lt;p&gt;As developers, we might not notice these shifts easily, especially if we're using fast internet connections where images load quickly or are already cached, making our testing less representative of actual user experience.&lt;/p&gt;

&lt;p&gt;Cumulative Layout Shift (CLS) is a crucial metric that tracks these unexpected layout shifts over a webpage's life. A layout shift happens when visible elements move abruptly. CLS gives insight into how frequently this occurs on a webpage.&lt;/p&gt;

&lt;p&gt;There are two kinds of layout shifts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unexpected Layout Shift&lt;/strong&gt;: This is unintentional and occurs without the developer's design intent. It negatively impacts the user experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expected Layout Shift&lt;/strong&gt;: This is intentional, usually triggered by user actions, like clicking a button to reveal more content or animations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The goal is to have no layout shifts on your webpage. However, a CLS score of 0.1 or less for 75% of all page loads is considered acceptable.&lt;/p&gt;

&lt;p&gt;To optimize for CLS, ensure that all your images have a fixed height and width, or add aspect ratio to them, so that the browser can reserve a place for them in the rendered page.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. First Input Delay (FID)
&lt;/h4&gt;

&lt;p&gt;Wrapping up Google’s Core Web Vitals is First Input Delay (FID). Have you ever been to a website where, after it finished loading, there was a noticeable delay when you tried to click a link or a button? This delay is what First Input Delay (FID) measures. FID tracks the time it takes for a webpage to respond to user interactions like clicks, taps, or key presses while it's still loading. To achieve a good FID score, the response time should be 100 milliseconds or less.&lt;/p&gt;

&lt;p&gt;To enhance First Input Delay (FID), focus on these key optimizations: Minimize the initial load of JavaScript, use Web Workers for complex tasks to free up the main thread, implement on-demand script loading, defer non-critical scripts with the &lt;code&gt;defer&lt;/code&gt; attribute, employ code-splitting and lazy loading techniques for efficient script management, and reduce the usage of pollyfills to lighten the JavaScript load.&lt;/p&gt;

&lt;p&gt;Summarized visual for the above metrics.&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%2Fagencyanalytics.com%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fimages.ctfassets.net%252Fdfcvkz6j859j%252F5TqrNxaPi09sdaM26MXEGt%252F05dc5bb819cd6645069e43b1b10d4f3f%252F13_Website_Performance_Metrics_To_Impress-Supporting-2.png%26w%3D3840%26q%3D75" 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%2Fagencyanalytics.com%2F_next%2Fimage%3Furl%3Dhttps%253A%252F%252Fimages.ctfassets.net%252Fdfcvkz6j859j%252F5TqrNxaPi09sdaM26MXEGt%252F05dc5bb819cd6645069e43b1b10d4f3f%252F13_Website_Performance_Metrics_To_Impress-Supporting-2.png%26w%3D3840%26q%3D75" title="Google’s Core Web Vitals" alt="Google’s Core Web Vitals" width="800" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  More Metrics
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;6.Page Load Time&lt;/strong&gt;&lt;br&gt;
This metric measures how long it takes for your client's website to fully render on a user's screen after their initial click.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7.Time to First Byte (TTFB)&lt;/strong&gt;&lt;br&gt;
TTFB is the duration it takes for a user's browser to obtain a data byte from a web server following a user’s initial click.&lt;br&gt;
As a benchmark, aim for a time to first byte of no more than 600 milliseconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8.Speed Index&lt;/strong&gt;&lt;br&gt;
Speed Index is the rate at which a webpage loads progressively over time. In contrast to FCP, this metric considers the entire page loading process rather than solely focusing on one part.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9.Time to Interactive (TTI)&lt;/strong&gt;&lt;br&gt;
TTI is the time it takes for your client’s website to become responsive to user interactions (e.g., getting a successful response after clicking a ‘Download’ button).&lt;/p&gt;

&lt;p&gt;While you can’t technically control how your visitors behave on your site, you certainly can optimize content to drive behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://web.dev/articles/vitals" rel="noopener noreferrer"&gt;https://web.dev/articles/vitals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://newsletter.unstacked.dev/p/web-performance-key-metrics-to-ensure" rel="noopener noreferrer"&gt;https://newsletter.unstacked.dev/p/web-performance-key-metrics-to-ensure&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>Code Coverage vs. Test Coverage</title>
      <dc:creator>Joseph Ndungi</dc:creator>
      <pubDate>Wed, 20 Sep 2023 11:50:38 +0000</pubDate>
      <link>https://dev.to/josephndungi/code-coverage-vs-test-coverage-25d1</link>
      <guid>https://dev.to/josephndungi/code-coverage-vs-test-coverage-25d1</guid>
      <description>&lt;p&gt;&lt;strong&gt;Code coverage&lt;/strong&gt; quantifies the portion of code lines activated during testing, while &lt;strong&gt;test coverage&lt;/strong&gt; gauges the degree to which tests encompass the software's capabilities or specifications.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code coverage reveals which code segments were triggered or bypassed.&lt;/li&gt;
&lt;li&gt;Test coverage highlights the functionalities assessed from an end-user perspective.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both metrics are vital in gauging the thoroughness of the testing procedure. However, individually, they provide only a fragmentary insight into the overall testing quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Code Coverage?
&lt;/h2&gt;

&lt;p&gt;This metric evaluates the lines of code activated by test cases, comparing total lines to those tested. It gauges how much of a program's source code is tested, aiming for higher coverage to reduce potential undetected bugs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def checkNumber(num):
 if num &amp;gt; 0:
   return "Positive"
 elif num &amp;lt; 0:
   return "Negative"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consider I have a test case that gives the input to the num variable as 10 and calls the function checkNumber. When it’s time to execute the if block, it runs line by line.&lt;/p&gt;

&lt;p&gt;Now since the if block was entered, the program exits after the return statement. So the code checked for two lines of code out of four, which means 50% of the code. In simple words, I covered just half of the code in my program, hence my code coverage is 50%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measuring Code Coverage
&lt;/h3&gt;

&lt;p&gt;Code coverage is often represented as a percentage indicating the portion of code executed during testing. It can be assessed through various methods such as statement, branch, condition, and function coverage.&lt;/p&gt;

&lt;p&gt;Above is an example of a conditional block to determine the coverage of the code. In testing terms, it is referred to as &lt;strong&gt;branch coverage&lt;/strong&gt;, since a decision-making block becomes a root of a branch, where one branch means the condition returned true and the other one is false.&lt;/p&gt;

&lt;h4&gt;
  
  
  Statement coverage
&lt;/h4&gt;

&lt;p&gt;The statement coverage in testing just verifies that all the executable lines of code have been implemented at least once.&lt;/p&gt;

&lt;p&gt;General formula for calculating statement coverage:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Statement coverage = (Number of executed statements / Total number of statements) * 100%&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Branch coverage
&lt;/h4&gt;

&lt;p&gt;Branch coverage measures how well testing has covered possible paths in the code. Represented as a percentage, it indicates the proportion of branches executed during testing, with 100% implying all branches were tested.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Branch coverage = (Number of branches executed / Total number of branches) * 100%&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Condition coverage
&lt;/h4&gt;

&lt;p&gt;Condition coverage tracks how often each Boolean expression in the code evaluates to both true and false during testing. It's commonly used in control structures like if statements and loops.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Condition coverage = (Number of conditions tested / Total number of conditions) * 100%&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Function coverage
&lt;/h4&gt;

&lt;p&gt;Function coverage is a code coverage metric used to assess how test cases exercise the functions in the program when tested. It responds to the question, "How many functions in my code have been called at least once?"&lt;/p&gt;

&lt;p&gt;The resulting ratio is then reported as a percentage, indicating the proportion of functions that have been tested. The formula for calculating function coverage is as follows:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Function coverage = (Number of called functions / Total number of functions) * 100%&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If a function is not invoked during testing, it's probably not being exercised correctly and may have flaws.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why perform Code Coverage?
&lt;/h3&gt;

&lt;p&gt;Code coverage is vital for code quality and reliability because it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Highlights untested or unused code.&lt;/li&gt;
&lt;li&gt;Evaluate the effectiveness of the test suite and pinpoint areas needing more testing.&lt;/li&gt;
&lt;li&gt;Indicates potential areas with bugs, prioritizing which code segments to inspect first.&lt;/li&gt;
&lt;li&gt;Helps identify and remove unnecessary "dead code."&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Advantages of Code Coverage
&lt;/h3&gt;

&lt;p&gt;Code coverage is a valuable measure for assessing the quality and completeness of tests. Other advantages of code coverage include the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Increased software reliability as a result of extensive testing.&lt;/li&gt;
&lt;li&gt;Shorter development cycles because developers can focus on testing critical portions of the code.&lt;/li&gt;
&lt;li&gt;Easier debugging because code coverage can assist in detecting bug-infested portions of code.&lt;/li&gt;
&lt;li&gt;Better code quality since code coverage identifies portions of code that may contain errors.&lt;/li&gt;
&lt;li&gt;Increased confidence in the codebase, as code coverage identifies untested code sections&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Limitations of Code Coverage
&lt;/h3&gt;

&lt;p&gt;Code coverage is a valuable metric for assessing code quality, but it has limitations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;High code coverage doesn't necessarily equate to high-quality code.&lt;/li&gt;
&lt;li&gt;Perfect code coverage doesn't guarantee the absence of defects or that all functionalities are tested.&lt;/li&gt;
&lt;li&gt;While code coverage measures code execution, it doesn't evaluate the test quality or overall system design.&lt;/li&gt;
&lt;li&gt;Relying solely on code coverage might result in overlooking poorly written or inefficient tests.&lt;/li&gt;
&lt;li&gt;Different tools may be required for various languages, adding to the implementation complexity.&lt;/li&gt;
&lt;li&gt;Implementing code coverage can be time-intensive and costly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Code Coverage Tools:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Coverage.Py for Python:&lt;/strong&gt; This tool provides details on the execution status of Python code components and can be installed using &lt;code&gt;pip install coverage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JaCoCo for Java:&lt;/strong&gt; Operated by JUnit tests, it offers results in binary format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testwell CTC++ for C++:&lt;/strong&gt; Downloadable from third-party sites, it stands out by indicating the execution frequency of each code line, rather than just its execution status.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What is Test Coverage?
&lt;/h2&gt;

&lt;p&gt;Test coverage plays a pivotal role in software creation and evaluation. It measures the extent of features tested, offering insights into potential risks. Unlike code coverage, which gauges the proportion of code executed in tests, test coverage focuses on feature validation. Ensuring comprehensive test coverage is vital for maintaining software quality and minimizing bugs.&lt;/p&gt;

&lt;p&gt;While code coverage provides insights into the software's inner mechanics and guides improvement areas, test coverage offers a user-centric perspective.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Test Coverage?
&lt;/h3&gt;

&lt;p&gt;The objective of assessing test coverage is to confirm that vital business needs are adequately tested, leaving no substantial testing voids. It entails evaluating if all pertinent scenarios, including edge cases, are addressed and if the tests are sufficient enough to spot potential problems. This evaluation aids in pinpointing testing deficiencies and highlighting areas for software improvement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measuring Test Coverage
&lt;/h3&gt;

&lt;p&gt;Test coverage focuses on assessing the extent to which business requirements are examined, distinguishing it from code coverage. It offers a subjective insight into the thoroughness of tests in relation to business requirements rather than presenting a precise numerical value.&lt;/p&gt;

&lt;p&gt;Key test coverage metrics include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Specification Coverage&lt;/strong&gt;: Verifies that every requirement of the software has undergone testing and is fulfilled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product Coverage&lt;/strong&gt;: Confirms that every feature and component within the product has been scrutinized.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk Coverage&lt;/strong&gt;: Highlights potential software vulnerabilities and checks if adequate tests have been conducted to address these concerns, minimizing potential challenges for users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functional Coverage&lt;/strong&gt;: Assures that the software's functional criteria are comprehensively tested.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test Execution Coverage&lt;/strong&gt;: Offers insights into test outcomes, guaranteeing that the software undergoes rigorous testing prior to its official release.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Benefits of Test Coverage
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Enhances software quality through comprehensive evaluation.&lt;/li&gt;
&lt;li&gt;Diminishes the risk of new defects and resolves existing ones.&lt;/li&gt;
&lt;li&gt;Easy to employ due to its black-box testing approach, requiring less code-specific knowledge.&lt;/li&gt;
&lt;li&gt;Pinpoints untested components, ensuring all product features are validated.&lt;/li&gt;
&lt;li&gt;Economizes on time and finances, reducing expensive bug corrections.&lt;/li&gt;
&lt;li&gt;Boosts confidence in the product by ensuring holistic examination.&lt;/li&gt;
&lt;li&gt;Facilitates quicker testing of code modifications, expediting release cycles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Drawbacks of Test Coverage
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Restricted scope, potentially missing issues like security risks or performance glitches.&lt;/li&gt;
&lt;li&gt;Might give an overconfident perception of code quality.&lt;/li&gt;
&lt;li&gt;The effectiveness hinges on the quality of the tests.&lt;/li&gt;
&lt;li&gt;Inadequate test coverage can be like navigating blindly, posing unnecessary risks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Test Coverage Tools:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;PyUnit:&lt;/strong&gt; A Python-based unit testing framework, PyUnit is renowned for facilitating the easy creation of test cases, tests, suites, and fixtures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JUnit for Java:&lt;/strong&gt; This open-source unit testing framework aids developers in tasks like writing, executing, and analyzing tests. Additionally, JUnit can handle user interface and regression testing.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Code Coverage vs. Test Coverage&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Quantitative vs. Qualitative&lt;/strong&gt;: Code coverage measures the percentage of lines of code tested, while test coverage gauges the depth and quality of tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Focus&lt;/strong&gt;: Code coverage looks at executed lines of code, while test coverage assesses the thoroughness of the entire testing activity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Approach&lt;/strong&gt;: Code coverage employs a white-box testing method, examining the internal logic of the code. Test coverage, on the other hand, uses a black-box approach, considering the software's external functionality.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;: Typically, code coverage is applied in unit tests, while test coverage is used in integration or system tests.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Code Coverage or Test Coverage?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The choice between the two depends on specific goals. Both are crucial for a holistic testing approach.&lt;/p&gt;

&lt;p&gt;Use code coverage during development to ensure every code line is tested. It's especially valuable for confirming specific code segments run during tests. On the other hand, when assessing the overall code efficacy, especially for performance, test coverage proves more beneficial, allowing swift issue resolution.&lt;/p&gt;

&lt;p&gt;Each project's unique demands will guide the decision to employ either, or potentially both, types of coverage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Both code and test coverage are critical metrics for determining software correctness. Code coverage verifies and validates code quality by evaluating the number of codes executed while running automated tests. It ensures that all parts of the code have been tested and that there are no defects or bugs present.&lt;/p&gt;

&lt;p&gt;In comparison, test coverage is a measure of the overall quality of the testing process. Both metrics are important, and deciding which to use depends on your specific scenario.&lt;/p&gt;

&lt;h3&gt;
  
  
  References:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://testsigma.com/blog/code-coverage-vs-test-coverage/" rel="noopener noreferrer"&gt;https://testsigma.com/blog/code-coverage-vs-test-coverage/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://saucelabs.com/resources/blog/code-coverage-vs-test-coverage" rel="noopener noreferrer"&gt;https://saucelabs.com/resources/blog/code-coverage-vs-test-coverage&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
