<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Didik Tri Susanto]]></title><description><![CDATA[My blog will cover various topics like software engineering, leadership, career, PHP, Laravel, web development, Golang]]></description><link>https://blog.didiktrisusanto.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 14 Sep 2026 02:18:27 GMT</lastBuildDate><atom:link href="https://blog.didiktrisusanto.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Implement Your Own Request Middleware for Go HTTP Server]]></title><description><![CDATA[I believed this topic is a lot in the internet but I want to write it anyway, because documenting your own learning journey and share it to the world is good right?

Request middleware performs some specific function on the HTTP request or response a...]]></description><link>https://blog.didiktrisusanto.dev/implement-your-own-request-middleware-for-go-http-server</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/implement-your-own-request-middleware-for-go-http-server</guid><category><![CDATA[Go Language]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[backend]]></category><category><![CDATA[REST API]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Fri, 02 May 2025 14:54:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/dfRrpfYD8Iw/upload/df019c79fc5fe16285276d0c13f748b1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I believed this topic is a lot in the internet but I want to write it anyway, because documenting your own learning journey and share it to the world is good right?</p>
<blockquote>
<p>Request middleware performs some specific function on the HTTP request or response at a specific stage in the HTTP pipeline before the business logic / app handler.</p>
</blockquote>
<p>Currently <code>middleware</code> is a common term but in different programming language or framework sometimes it called <code>filter</code>. For illustration could be like this</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1744646772079/0a4e75bd-c291-430a-b986-5853a12f21d4.png" alt="http request middleware diagram" class="image--center mx-auto" /></p>
<p>As you can see, middleware could be layered for more than one before hitting app handler or business logic then later it will return the response. HTTP Request comes in to first layer or middleware, check the necessary and if all conditions are passed then it allowed to continue the journey. If failed, it could also returned the failed response anyway.</p>
<h2 id="heading-basic-middleware">Basic Middleware</h2>
<p>Let say we have simple HTTP API server</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">rootHandler</span><span class="hljs-params">()</span> <span class="hljs-title">http</span>.<span class="hljs-title">Handler</span></span> {
    <span class="hljs-keyword">return</span> http.HandlerFunc(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
        resp := defaultResponse{
            Status:      <span class="hljs-string">"OK"</span>,
            Description: <span class="hljs-string">"Success"</span>,
        }

        j, _ := json.Marshal(resp)
        w.Header().Add(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>)
        w.Write(j)
    })
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    mux := http.NewServeMux()

    mux.Handle(<span class="hljs-string">"GET /"</span>, rootHandler())

    s := &amp;http.Server{
        Addr:    <span class="hljs-string">":8080"</span>,
        Handler: mux,
    }

    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>if we run the program <code>go run main.go</code> and visit <code>http://localhost:8080</code> it will return 200 success with JSON response:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"status"</span>: <span class="hljs-string">"OK"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Success"</span>
}
</code></pre>
<p>Now lets create our first middleware. Our middleware is to write a simple log that indicating the request is passing through. The middleware should be a function like this</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">logMiddleware</span><span class="hljs-params">(next http.Handler)</span> <span class="hljs-title">http</span>.<span class="hljs-title">Handler</span></span> {
    <span class="hljs-keyword">return</span> http.HandlerFunc(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
        log.Print(<span class="hljs-string">"Executing logMiddleware"</span>)
        next.ServeHTTP(w, r)
    })
}
</code></pre>
<p>Then we will modify the <code>mux</code> handler to use the middleware</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    mux := http.NewServeMux()
    mux.Handle(<span class="hljs-string">"/"</span>, logMiddleware(rootHandler()))
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>The result is when we’re visiting the API, it also wrote the log that already specified in the middleware logic.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746195804735/4cfef6df-7546-44c1-9be0-f82a3a86a504.png" alt="screenshot of running program with log middleware" class="image--center mx-auto" /></p>
<h2 id="heading-adding-more-middleware">Adding More Middleware</h2>
<p>Interesting. Now try to add more middleware. This time we will check if the API request header contains <code>x-signature</code>, if not exists then we will return error.</p>
<p>Lets create the middleware function</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">requestSignatureMiddleware</span><span class="hljs-params">(next http.Handler)</span> <span class="hljs-title">http</span>.<span class="hljs-title">Handler</span></span> {
    <span class="hljs-keyword">return</span> http.HandlerFunc(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
        log.Print(<span class="hljs-string">"Executing requestSignatureMiddleware"</span>)
        h := r.Header.Get(<span class="hljs-string">"x-signature"</span>)
        <span class="hljs-keyword">if</span> h == <span class="hljs-string">""</span> {
            resp := errorResponse{
                Status:  http.StatusUnauthorized,
                Error:   <span class="hljs-string">"INVALID_MISSING_SIGNATURE"</span>,
                Message: <span class="hljs-string">"missing request signature"</span>,
            }

            j, _ := json.Marshal(resp)
            w.Header().Add(<span class="hljs-string">"Content-Type"</span>, <span class="hljs-string">"application/json"</span>)
            w.WriteHeader(http.StatusUnauthorized)
            w.Write(j)
            <span class="hljs-keyword">return</span>
        }
        next.ServeHTTP(w, r)
    })
}
</code></pre>
<p>We want to call <code>logMiddleware</code> first then continue to <code>requestSignatureMiddleware</code>. Our <code>mux</code> handler would be look like this now</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    mux := http.NewServeMux()
    mux.Handle(<span class="hljs-string">"/"</span>, logMiddleware(requestSignatureMiddleware(rootHandler())))
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>Here’s the result of the combined middlewares</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1746196539301/ed671867-fc7f-4a88-a9d5-f13312d46a61.png" alt="screenshot of running multiple middlewares" class="image--center mx-auto" /></p>
<ul>
<li><p>First request without <code>x-signature</code> header will returned <code>401</code> HTTP status</p>
</li>
<li><p>Second request with additional <code>x-signature</code> header passed middleware logic and returned <code>200</code> HTTP status.</p>
</li>
</ul>
<h2 id="heading-cleaning-up">Cleaning Up</h2>
<p>You may notice that our <code>mux</code> handler with multiple middlewares is little bit messy because it is calling function into function. What if we have many middlewares?</p>
<p><code>logMiddleware(requestSignatureMiddleware(authMiddleware(rateLimitMiddleware(rootHandler)))))</code></p>
<p>Hard to see.</p>
<p>Here’s what we can do, create a <code>chain</code> function to loop all middleware functions. You can named the function whatever you want.</p>
<pre><code class="lang-go"><span class="hljs-comment">// Middleware type for cleaner middleware chaining</span>
<span class="hljs-keyword">type</span> Middleware <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(http.Handler)</span> <span class="hljs-title">http</span>.<span class="hljs-title">Handler</span></span>

<span class="hljs-comment">// Chain creates a single middleware from multiple middlewares</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">Chain</span><span class="hljs-params">(middlewares ...Middleware)</span> <span class="hljs-title">Middleware</span></span> {
    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(next http.Handler)</span> <span class="hljs-title">http</span>.<span class="hljs-title">Handler</span></span> {
        <span class="hljs-keyword">for</span> i := <span class="hljs-built_in">len</span>(middlewares) - <span class="hljs-number">1</span>; i &gt;= <span class="hljs-number">0</span>; i-- {
            next = middlewares[i](next)
        }
        <span class="hljs-keyword">return</span> next
    }
}
</code></pre>
<p>Initiate the <code>Chain</code> with our middlewares and finally call it in our <code>mux</code> handler.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    mux := http.NewServeMux()

    <span class="hljs-comment">// Create a chain of middlewares</span>
    middlewareChain := Chain(
        <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(next http.Handler)</span> <span class="hljs-title">http</span>.<span class="hljs-title">Handler</span></span> { <span class="hljs-keyword">return</span> logMiddleware(next) },
        <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(next http.Handler)</span> <span class="hljs-title">http</span>.<span class="hljs-title">Handler</span></span> { <span class="hljs-keyword">return</span> requestSignatureMiddleware(next) },
    <span class="hljs-comment">// Add more middlewares here as needed</span>
    )

    mux.Handle(<span class="hljs-string">"GET /"</span>, middlewareChain(rootHandler()))
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>Now it’s more manageable and it will still got same result as before.</p>
<p>So here’s the full code of this experiment <a target="_blank" href="https://gist.github.com/didikz/98de868f20887285f5aabc80f4dddcfa">https://gist.github.com/didikz/98de868f20887285f5aabc80f4dddcfa</a></p>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[How To Do Graceful Shutdown for Go HTTP Server]]></title><description><![CDATA[Long time no see. After took a long break, I finally started to writing again. I missed code with Go so I decided to share about Graceful shutdown in this post.
Graceful shutdown is safer and proper way to shutdown your HTTP server especially in Go w...]]></description><link>https://blog.didiktrisusanto.dev/how-to-do-graceful-shutdown-for-go-http-server</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/how-to-do-graceful-shutdown-for-go-http-server</guid><category><![CDATA[Go Language]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[REST API]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Tue, 08 Apr 2025 14:29:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/4TsDHnlUfqc/upload/116388c5a64c213b25d0c970e36d76c9.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Long time no see. After took a long break, I finally started to writing again. I missed code with Go so I decided to share about Graceful shutdown in this post.</p>
<p>Graceful shutdown is safer and proper way to shutdown your HTTP server especially in Go where we usually managed the server script by ourself.</p>
<blockquote>
<p>The “Graceful shutdown function” facilitates this process, allowing the server to transition smoothly without abruptly terminating active connections.</p>
</blockquote>
<p>The most basic and simple way to run the HTTP server in Go is like this</p>
<pre><code class="lang-go">s := &amp;http.Server{
    Addr:           <span class="hljs-string">":8080"</span>,
    Handler:        mux,
    ReadTimeout:    <span class="hljs-number">10</span> * time.Second,
    WriteTimeout:   <span class="hljs-number">10</span> * time.Second,
}

log.Fatal(s.ListenAndServe())
</code></pre>
<p><code>s.ListenAndServe()</code> is called to run the server and listen incoming http requests. Yes this works, but when we’re talking about production system, we need more proper way using graceful shutdown to prevent unexpected behavior when the server needs to be stopped.</p>
<p>Here’s how graceful shutdown would be</p>
<pre><code class="lang-go">    s := &amp;http.Server{
        Addr:    <span class="hljs-string">":8080"</span>,
        Handler: mux,
    }

    stop := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> os.Signal, <span class="hljs-number">1</span>)
    signal.Notify(stop, os.Interrupt, syscall.SIGTERM)

    <span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {
        <span class="hljs-keyword">if</span> err := s.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
            log.Fatal(err)
        }
        log.Println(<span class="hljs-string">"Stopped serving new connections"</span>)
    }()

    &lt;-stop
    log.Println(<span class="hljs-string">"Shutting down gracefully..."</span>)

    shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), <span class="hljs-number">10</span>*time.Second)
    <span class="hljs-keyword">defer</span> shutdownRelease()

    <span class="hljs-keyword">if</span> err := s.Shutdown(shutdownCtx); err != <span class="hljs-literal">nil</span> {
        log.Printf(<span class="hljs-string">"server shutdown error: %v\n"</span>, err)
    }

    log.Println(<span class="hljs-string">"Server stoppped"</span>)
</code></pre>
<p>It’s using goroutine and channel to notify server if there is termination signal. More example of this implementation at my fun project <a target="_blank" href="https://blog.didiktrisusanto.dev/building-simple-real-time-system-monitor-using-go-htmx-and-web-socket">simple real-time system monitoring using Go &amp; HTMX</a>.</p>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Unemployed #37 - End of Search and Incoming Offers]]></title><description><![CDATA[So my unemployed journey was stopped at #10 because many things happened and today at #37 or 57 days since my last day at work, I finally secured some job offers and agreed to join at a Singapore based company in 2nd January 2025 which I become first...]]></description><link>https://blog.didiktrisusanto.dev/unemployed-37-end-of-search-and-incoming-offers</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/unemployed-37-end-of-search-and-incoming-offers</guid><category><![CDATA[Career]]></category><category><![CDATA[job search]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Tue, 26 Nov 2024 15:31:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Ua-agENjmI4/upload/c3fff6fcd1de577ac907f058cdbeeb11.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So my unemployed journey was stopped at #10 because many things happened and today at #37 or 57 days since my last day at work, I finally secured some job offers and agreed to join at a Singapore based company in 2nd January 2025 which I become first Indonesian Developer who will be joined them!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732619118039/1379edc3-113c-4961-baa4-1d5c9dfc95ba.png" alt="job offer" class="image--center mx-auto" /></p>
<p>Actually another offer came within the same week, made me took quite hard to choose which offer that will be best for me. By this decision I withdrawn the other offer.</p>
<h1 id="heading-offering-considerations">Offering Considerations</h1>
<p>I got 3 offers from companies and let me breakdown some points of them</p>
<p><strong>D Company</strong></p>
<ul>
<li><p>One of my ex employers so I know most of their working cultures</p>
</li>
<li><p>The position is Head of Engineering</p>
</li>
<li><p>The salary would be significantly dropped from my current salary</p>
</li>
<li><p>On site but only around 10 minutes from my home</p>
</li>
<li><p>Has some interesting management problems that could shape my managerial skills more</p>
</li>
</ul>
<p><strong>S Company</strong></p>
<ul>
<li><p>Singapore based company in pharmacy business</p>
</li>
<li><p>Latest funding was Series B and quite stable</p>
</li>
<li><p>The role is senior associate full stack, would be PHP &amp; React</p>
</li>
<li><p>Base salary almost same with my current salary</p>
</li>
<li><p>Work device provided by company</p>
</li>
<li><p>Additional private insurance as work benefit</p>
</li>
<li><p>Full remote</p>
</li>
</ul>
<p><strong>O Company</strong></p>
<ul>
<li><p>Singapore based company, social commerce SaaS</p>
</li>
<li><p>Relatively new founded company. Latest funding was pre-seed but they said current business is good and quite sustainable</p>
</li>
<li><p>Will expand the business to Indonesia and I am their first hire as Indonesian Developer</p>
</li>
<li><p>The role is senior PHP engineer</p>
</li>
<li><p>Base salary is higher than current salary</p>
</li>
<li><p>No work device (BYOD)</p>
</li>
<li><p>Full remote</p>
</li>
</ul>
<p>As you can see the last two offers are close enough with my career goal as I still want to <a target="_blank" href="https://blog.didiktrisusanto.dev/career-retrospective-individual-contributor-or-engineering-management">pursue IC track rather than managerial track</a> although in the future I probably want to change my direction to managerial role, but that’s not my current priority.</p>
<p>At first, I tent to S company as my next company if I passed the hiring process because the stability and I impressed by their leader. Then the O company gave me an offer with higher salary also they showed me many extra effort just to get me hired.</p>
<p>Spent my whole weekend to think about it because for me it was hard to decide. Then I decided to join the O company and withdrawn the offer from S company because:</p>
<ul>
<li><p>more higher salary since I need more additional income for some plans in the near future</p>
</li>
<li><p>O company hiring process came first, although it took too long but they really showed effort to get me hired</p>
</li>
<li><p>More potential to grow especially in career since they have plan to expand to Indonesia and I am their first engineer in Indonesia</p>
</li>
</ul>
<h1 id="heading-the-job-hunting-journey">The Job Hunting Journey</h1>
<p>A little bit retrospective to see how I landed on a new job after applied to many job vacancies. I tried to tracked my job hunting data (recent 3-4 months) and made a data visualization as a summary.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732634095907/72ef8e57-4c2c-4678-b2e6-435af6a27fcd.png" alt="percentage of job hunting status result" class="image--center mx-auto" /></p>
<p>So far I applied to 58 jobs (could be more) and surprisingly most of them are <code>no answer</code> or being ghosted by recruiter. 24 applications are failed, mostly on screening process and few of them failed after technical test or interview.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732634409706/f7a991e8-9a57-460d-a8f2-f7e11af61c44.png" alt class="image--center mx-auto" /></p>
<p>If I expand the data little bit especially the portion of which stage was contributed more to my hiring process. Ended up screening stage is the most contributed. Could be because no answer or simply they assumed I have no qualifications to went the next stage.</p>
<blockquote>
<p>I think this is why we need to tweak our CV or resume to increase the chance of recruiter picked up our application. But portfolios, experiences, and tech stack could be the big factor for them to decide if we passed the screening or not</p>
</blockquote>
<h1 id="heading-what-is-next">What is Next?</h1>
<p>So yeah, this unemployed journal is complete for now. The next post, I will focus on regular content that is not related with the job hunting.</p>
<p>Cheers!</p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-10-htmx">← Unemployed #10 - HTMX</a></p>
]]></content:encoded></item><item><title><![CDATA[Building Simple Real-Time System Monitor using Go, HTMX, and Web Socket]]></title><description><![CDATA[I was finding a fun project to work with Go, HTMX, and Tailwwindcss and ended up built a simple real-time web based system monitor with the power of web socket. Here’s the result.

It shows system information, memories, disk, CPU, and running process...]]></description><link>https://blog.didiktrisusanto.dev/building-simple-real-time-system-monitor-using-go-htmx-and-web-socket</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/building-simple-real-time-system-monitor-using-go-htmx-and-web-socket</guid><category><![CDATA[Go Language]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[websockets]]></category><category><![CDATA[htmx]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Mon, 11 Nov 2024 05:48:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731304048441/44157838-b348-4242-b1e5-82ee305ab82a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I was finding a fun project to work with Go, HTMX, and Tailwwindcss and ended up built a simple real-time web based system monitor with the power of web socket. Here’s the result.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731297504702/1075ce7e-8290-4448-8f33-60801aaf9fe8.png" alt="screenshot of simple web based system monitor" class="image--center mx-auto" /></p>
<p>It shows system information, memories, disk, CPU, and running processes, updated automatically every 5 seconds.</p>
<p>I’ll break down the code little bit in this post.</p>
<h1 id="heading-stacks">Stacks</h1>
<ul>
<li><p>Go 1.23.2</p>
</li>
<li><p><a target="_blank" href="https://htmx.org/">Htmx</a></p>
</li>
<li><p><a target="_blank" href="https://tailwindcss.com/">Tailwindcss</a></p>
</li>
<li><p><a target="_blank" href="https://pkg.go.dev/github.com/shirou/gopsutil/v4">Gopsutil</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/coder/websocket">Websocket</a></p>
</li>
<li><p><a target="_blank" href="https://htmx.org/extensions/ws/">Htmx websocket extension</a></p>
</li>
</ul>
<h1 id="heading-http-server">HTTP Server</h1>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> HttpServer <span class="hljs-keyword">struct</span> {
    subscriberMessageBuffer <span class="hljs-keyword">int</span>
    Mux                     http.ServeMux
    subscribersMutex        sync.Mutex
    subscribers             <span class="hljs-keyword">map</span>[*subscriber]<span class="hljs-keyword">struct</span>{}
}

<span class="hljs-keyword">type</span> subscriber <span class="hljs-keyword">struct</span> {
    msgs <span class="hljs-keyword">chan</span> []<span class="hljs-keyword">byte</span>
}
</code></pre>
<p>It’s quite straightforward. <code>HttpServer</code> contains a <code>http.ServeMux</code> as http handler and <code>subscribers</code> for web socket broadcasting later. <code>subscriber</code> is simply has <code>msgs</code> channel for data update.</p>
<p>Since it only needs to serve a single HTML file, then it needs URL to show the page, and one URL for web socket connection.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NewHttpServer</span><span class="hljs-params">()</span> *<span class="hljs-title">HttpServer</span></span> {
    s := &amp;HttpServer{
        subscriberMessageBuffer: <span class="hljs-number">10</span>,
        subscribers:             <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[*subscriber]<span class="hljs-keyword">struct</span>{}),
    }

    s.Mux.Handle(<span class="hljs-string">"/"</span>, http.FileServer(http.Dir(<span class="hljs-string">"./views"</span>)))
    s.Mux.HandleFunc(<span class="hljs-string">"/ws"</span>, s.subscribeHandler)
    <span class="hljs-keyword">return</span> s
}
</code></pre>
<h1 id="heading-web-socket-connection-amp-subscriber">Web Socket Connection &amp; Subscriber</h1>
<p>Endpoint <code>/ws</code> will handling web socket connection and managing a subscriber. First it will initiate a new subscriber and added it to a map in the http server structure. <code>Lock</code> will be used to prevent race condition since we will use go routine later.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *HttpServer)</span> <span class="hljs-title">subscribeHandler</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
    err := s.subscribe(r.Context(), w, r)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Println(err)
        <span class="hljs-keyword">return</span>
    }
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *HttpServer)</span> <span class="hljs-title">addSubscriber</span><span class="hljs-params">(subscriber *subscriber)</span></span> {
    s.subscribersMutex.Lock()
    s.subscribers[subscriber] = <span class="hljs-keyword">struct</span>{}{}
    s.subscribersMutex.Unlock()
    fmt.Println(<span class="hljs-string">"subscriber added"</span>, subscriber)
}
</code></pre>
<p>Web socket is starting accept a connection and via loop, we will detect an incoming channel <code>msgs</code> from subscriber and write it to web socket.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *HttpServer)</span> <span class="hljs-title">subscribe</span><span class="hljs-params">(ctx context.Context, w http.ResponseWriter, r *http.Request)</span> <span class="hljs-title">error</span></span> {
    <span class="hljs-keyword">var</span> c *websocket.Conn
    subscriber := &amp;subscriber{
        msgs: <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> []<span class="hljs-keyword">byte</span>, s.subscriberMessageBuffer),
    }

    s.addSubscriber(subscriber)

    c, err := websocket.Accept(w, r, <span class="hljs-literal">nil</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> err
    }

    <span class="hljs-keyword">defer</span> c.CloseNow()

    ctx = c.CloseRead(ctx)
    <span class="hljs-keyword">for</span> {
        <span class="hljs-keyword">select</span> {
        <span class="hljs-keyword">case</span> msg := &lt;-subscriber.msgs:
            ctx, cancel := context.WithTimeout(ctx, time.Second)
            <span class="hljs-keyword">defer</span> cancel()
            err := c.Write(ctx, websocket.MessageText, msg)
            <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
                <span class="hljs-keyword">return</span> err
            }
        <span class="hljs-keyword">case</span> &lt;-ctx.Done():
            <span class="hljs-keyword">return</span> ctx.Err()
        }
    }
}
</code></pre>
<h1 id="heading-auto-update">Auto Update</h1>
<p>Auto update the system info data is handled by go routine. We will build a html response that will be sent via web socket and htmx will handle the update on the html side.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    fmt.Println(<span class="hljs-string">"Starting system monitor"</span>)
    s := server.NewHttpServer()

    <span class="hljs-keyword">go</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(s *server.HttpServer)</span></span> {
        <span class="hljs-keyword">for</span> {
            hostStat, _ := host.Info()
            timestamp := time.Now().Format(<span class="hljs-string">"2006-01-02 15:04:05"</span>)
            html := <span class="hljs-string">`
            &lt;span hx-swap-oob="innerHTML:#data-timestamp"&gt;`</span> + timestamp + <span class="hljs-string">`&lt;/span&gt;
            &lt;span hx-swap-oob="innerHTML:#system-hostname"&gt;`</span> + hostStat.Hostname + <span class="hljs-string">`&lt;/span&gt;
            &lt;span hx-swap-oob="innerHTML:#system-os"&gt;`</span> + hostStat.OS + <span class="hljs-string">`&lt;/span&gt;
            `</span>
            s.Broadcast([]<span class="hljs-keyword">byte</span>(html))
            time.Sleep(time.Second * <span class="hljs-number">5</span>)
        }
    }(s)
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>Syntax <code>hx-swap-oob="innerHTML:#data-timestamp"</code> in htmx is tell us that swap a component inside <code>data-timestamp</code> id in our HTML. All swapping mechanism will be the same for other system information components.</p>
<p>All swappable <code>html</code> components will be sent via <code>Broadcast(msg)</code> method and later will be sent via channel every 5 seconds.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *HttpServer)</span> <span class="hljs-title">Broadcast</span><span class="hljs-params">(msg []<span class="hljs-keyword">byte</span>)</span></span> {
    s.subscribersMutex.Lock()
    <span class="hljs-keyword">for</span> subscriber := <span class="hljs-keyword">range</span> s.subscribers {
        subscriber.msgs &lt;- msg
    }
    s.subscribersMutex.Unlock()
}
</code></pre>
<h1 id="heading-the-htmx-view">The HTMX View</h1>
<p>It’s plain HTML file and for Tailwindcss I simple used CDN for that</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.tailwindcss.com"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>Same idea for HTMX and web socket extension for using CDN</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://unpkg.com/htmx.org@2.0.3"</span> <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha384-0895/pl2MU10Hqc6jd4RvrthNlDiE9U1tWmX7WRESftEDRosgxNsQG/Ze9YMRzHq"</span> <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://unpkg.com/htmx-ext-ws@2.0.1/ws.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<h1 id="heading-how-to-connect-to-the-web-socket">How to connect to the web socket?</h1>
<p>The system monitor page is expected to receives all the data by web socket so I can set it from the main div container. Specify <code>hx-ext=”ws”</code> to tell HTMX for using web socket extension and <code>ws-connect=”/ws”</code> to tell web socket to connect via <code>/ws</code> URL.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"bg-gray-700 text-white"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container mx-auto p-8"</span> <span class="hljs-attr">hx-ext</span>=<span class="hljs-string">"ws"</span> <span class="hljs-attr">ws-connect</span>=<span class="hljs-string">"/ws"</span>&gt;</span>
   <span class="hljs-comment">&lt;!-- all the data --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre>
<h1 id="heading-full-code">Full Code</h1>
<p>Here is the full version of the code <a target="_blank" href="https://github.com/didikz/gosysmon-web">https://github.com/didikz/gosysmon-web</a> and you may want to play around with your own version.</p>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Unemployed #10 - HTMX]]></title><description><![CDATA[No update on the job hunting today. An ex coworker gave me a job vacancy from Switzerland and didn’t took any action yet.
Decided to continue on my playground building a short url service using Go. The first one was how to implement templ in go as vi...]]></description><link>https://blog.didiktrisusanto.dev/unemployed-10-htmx</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/unemployed-10-htmx</guid><category><![CDATA[Career]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[htmx]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Fri, 18 Oct 2024 15:25:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1729262680370/4a1eb490-108e-406d-8df6-227a757bc37e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>No update on the job hunting today. An ex coworker gave me a job vacancy from Switzerland and didn’t took any action yet.</p>
<p>Decided to continue on my playground building a short url service using Go. The first one was how to implement <a target="_blank" href="https://templ.guide/">templ</a> in go as view template. It was interesting because we can separate the view components and made it reusable.</p>
<pre><code class="lang-go"><span class="hljs-keyword">package</span> components

templ Header() {
    &lt;header class=<span class="hljs-string">"bg-gray-800 py-4"</span>&gt;
        &lt;div class=<span class="hljs-string">"container mx-auto px-4"</span>&gt;
            &lt;h1 class=<span class="hljs-string">"text-2xl font-bold"</span>&gt;Goshu&lt;/h1&gt;
        &lt;/div&gt;
    &lt;/header&gt;
}
</code></pre>
<p>That component later could be called into other template by <code>@components.Head()</code>. As a person who familiar with <a target="_blank" href="https://laravel.com/docs/11.x/blade">Laravel Blade</a>, separating components seems good idea.</p>
<p>Anyway the landing looks like this now:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1729264606705/543a2322-51f1-4026-903d-a97a60ef7412.png" alt class="image--center mx-auto" /></p>
<p>Tried <a target="_blank" href="https://htmx.org/">htmx</a> on POST form got successfully implemented but I struggled little bit. It’s been a while I played around with html forms because for recent years mostly related with REST API and platform related development. Also I need to adapt on the htmx syntax including all the events, attributes, etc.</p>
<p>However I still couldn’t parse the JSON response on the views side once the form submitted successfully. Definitely will be my next research on that.</p>
<p>That’s it, have a good day!</p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-9-update-on-job-hunting">← Unemployed #9 - Update On Job Hunting</a></p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-37-end-of-search-and-incoming-offers">Unemployed #37 - End of Search and Incoming Offers →</a></p>
]]></content:encoded></item><item><title><![CDATA[Unemployed #9 - Update on Job Hunting]]></title><description><![CDATA[Commerce Company
Sooooooooo, my contact on Glints already scheduled for 2nd interview round at 22th Oct next week. Took more than one month to get this progress after first interview. I already set my expectation to low on this, but still got hope be...]]></description><link>https://blog.didiktrisusanto.dev/unemployed-9-update-on-job-hunting</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/unemployed-9-update-on-job-hunting</guid><category><![CDATA[Career]]></category><category><![CDATA[job search]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Thu, 17 Oct 2024 13:49:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/fY8Jr4iuPQM/upload/4fa099a582a7990e94cbb0eecd44f31a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-commerce-company">Commerce Company</h1>
<p>Sooooooooo, my contact on Glints already scheduled for 2nd interview round at 22th Oct next week. Took more than one month to get this progress after first interview. I already set my expectation to low on this, but still got hope because last week she said my next interview schedule will be arranged and just got the info today.</p>
<p>Let see how the interview goes next week. I’ll share the detail later.</p>
<h1 id="heading-ed-tech-company">Ed-Tech Company</h1>
<p>Surprisingly this morning a recruiter company reached me for a senior role in one of SaaS company which has product for child education. Interesting because long back then I interested with educational topic. I tried to join several edtech company based in Indonesia but failed to get hired.</p>
<blockquote>
<p>Definitely skill issue.</p>
</blockquote>
<p>However this afternoon we discussed about the role. It is Singapore based company but has diverse team in Indonesia, Singapore, and Vietnam. Their main tech stack are Javascript and Go but there’s a PHP part that needs my expertise on that.</p>
<p>The discussion were about:</p>
<ul>
<li><p>Company detail, team size, product, and tech stack</p>
</li>
<li><p>History of my experience</p>
</li>
<li><p>Working preference and arrangement</p>
</li>
<li><p>Previous and expected compensation benefit</p>
</li>
<li><p>Hiring process</p>
</li>
<li><p>Career expectation</p>
</li>
</ul>
<p>Overall hiring stage would be:</p>
<ul>
<li><p>Technical test (home test)</p>
</li>
<li><p>Panel technical interview with tech lead and engineering manager</p>
</li>
<li><p>Final interview with CTO</p>
</li>
<li><p>Offering</p>
</li>
</ul>
<p>Once all sorted out and I agreed to continue, he will proposed me as their candidate and if all good then they will send the technical assessment. I’ll wait on that.</p>
<p>I also told him that I already committed to freelance project with my friend so in case I got hired, I need time to notify my client. He was ok with that.</p>
<p>Another good part is, he already has my profile. In case I failed to this company, he will find another company who might suitable with my expertise.</p>
<h1 id="heading-freelance-update">Freelance Update</h1>
<p>No update yet. I already said that I would ready by next week and they still not sending any term or contract whatsoever.</p>
<h1 id="heading-upwork">Upwork</h1>
<p>Naah, my connects is running low. I won’t use it until it necessary and worth to spend for potential projects.</p>
<p>That’s it. Have a great day, Folks!</p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-8-an-unexpected-offer">← Unemployed #8 - An Unexpected Offer</a></p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-10-htmx?showSharer=true">Unemployed #10 - HTMX →</a></p>
]]></content:encoded></item><item><title><![CDATA[Unemployed #8 - An Unexpected Offer]]></title><description><![CDATA[The CTO of my ex company at Malang invited me to discuss about technical issue for their current project. I went to their office by lunch time. Little bit nostalgic since I grew up for more than 5 years with the company. I met and talked with my ex p...]]></description><link>https://blog.didiktrisusanto.dev/unemployed-8-an-unexpected-offer</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/unemployed-8-an-unexpected-offer</guid><category><![CDATA[Career]]></category><category><![CDATA[job search]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Tue, 15 Oct 2024 17:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/u7r-VFdvQk8/upload/55ebfaa8c4bda8be93f7945d9b41c7be.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The CTO of my ex company at Malang invited me to discuss about technical issue for their current project. I went to their office by lunch time. Little bit nostalgic since I grew up for more than 5 years with the company. I met and talked with my ex partners who used to be worked together.</p>
<p>Still amazed me how one of them is actually my first hire as web developer back then when we’re still 5 peoples IT agency now becomes a project director. I remember he was a fresh graduate from college and I tested him with some basic CRUD using PHP. Couldn’t solve related with JQuery stuff but I decided to hired him.</p>
<p>It has good feeling knowing our work partner grows to a certain point, remembering who they were at the start. Also remember I was mentioning my <a target="_blank" href="https://hashnode.com/post/cm24utxvm000108jx6wnl0u3b">high school intern then becoming a solid developer</a>. Time flies.</p>
<h1 id="heading-system-performance-discussion">System Performance Discussion</h1>
<p>It was discussion about their web application performance and find what approach to solved it. The web application which supported client’s ERP system is actually working normally but during the peak usage, it started slow. The latency could be 5 minutes long which is unacceptable. The possibility is probably coming from slow queries due growing data in database or infra capacity issue.</p>
<p>Application hosted in a single server along side with the database server so we’re assumed that particular setup contributed to bottleneck during peak. Separate the database or even making it a master-replica is impossible right now because it was client’s IT department authority and the CTO won’t bother to make that requests. Just wasting a time.</p>
<p>We were discuss a lot regarding database issue. I suggested them to use profiling tools to track the bottleneck, otherwise it might ended up solving the wrong problem. They already had slow query and some profiling in <a target="_blank" href="https://sentry.io/welcome/">Sentry</a> but although it already been improved but slowness still occurred. I suggested to check database usage especially on <a target="_blank" href="https://medium.com/@beeindian04/threading-in-databases-strategies-for-improving-performance-and-overcoming-common-issues-82076315a297">threads</a> and lock time. At Flip, I experienced many performance issues related with threads and lock time went high although the CPU and the memory was low. Indexing, connection pool, query strategy also been discussed.</p>
<h1 id="heading-sudden-offer">Sudden Offer</h1>
<p>Conversation led us to I said that I already resigned from Flip and looking for new opportunities. Suddenly he offered me a same position as my last position before I leaved the company, this time with slightly different title and scope of work. They’re facing some managerial issue in engineering department and needs me to help them sorted the problem.</p>
<p>This put a dilemma on me. I need a job but my priority is on IC position because I still wants to going deeper to technical expertise. I wrote some of my reasons on two separate blog posts why I choose IC as my current main goal:</p>
<ul>
<li><p><a target="_blank" href="https://blog.didiktrisusanto.dev/career-retrospective-individual-contributor-or-engineering-management">Career Retrospective: Individual Contributor or Engineering Management</a></p>
</li>
<li><p><a target="_blank" href="https://blog.didiktrisusanto.dev/finding-the-archetype">Finding the Archetype</a></p>
</li>
</ul>
<p>Being managerial position is not my top of priority but it still valid option as career journey.</p>
<p>I answered that I still want to grind on technical position as IC though and I also mentioned that I already committed to a full time project with my ex coworker which could be took 3-4 months long. The other thing is I also still on hiring process on a commerce company based on Singapore which wants to expand their engineering team to Indonesia which still being my interesting choice.</p>
<h1 id="heading-the-choice">The Choice</h1>
<p>Yea, at the end I have to consider this offer so I said that I needs time to think and he was fine with it. But I offered back to him if I am available to part-time development job if needed. It could be the opportunity to “warm up” or observe the company condition before I take final decision whether want to join as managerial position or not.</p>
<blockquote>
<p>So ya, I think for couple months forward I will focus on the upcoming project or waiting hiring result from Singapore company or even another interesting opportunity.</p>
</blockquote>
<p><a target="_blank" href="https://hashnode.com/post/cm2bb14pa000809kw29mh7s8b">← Unemployed #7 - The Effort of Registering Vehicle Registration Number</a></p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-9-update-on-job-hunting?showSharer=true">Unemployed #9 - Update on Job Hunting →</a></p>
]]></content:encoded></item><item><title><![CDATA[Unemployed #7 - The Effort of Registering Vehicle Registration Number]]></title><description><![CDATA[It is not related with programming or software engineering topic. Yesterday I spent 4+ hours driving back and forth to my hometown to get my vehicle registration number since my ID still addressed at my hometown. I bought this used car early this yea...]]></description><link>https://blog.didiktrisusanto.dev/unemployed-7-the-effort-of-registering-vehicle-registration-number</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/unemployed-7-the-effort-of-registering-vehicle-registration-number</guid><category><![CDATA[Career]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Mon, 14 Oct 2024 17:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/44h2kiN53g8/upload/5b8a3f838d3c441730180a55ff19e37d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It is not related with programming or software engineering topic. Yesterday I spent 4+ hours driving back and forth to my hometown to get my vehicle registration number since my ID still addressed at my hometown. I bought this used car early this year and need to transfer car ownership by changing all the administrative and new registration number.</p>
<p>I can do this by using we called “<em>biro jasa</em>” so I just need to sit tight and let them work, I’ll get the result later. But the downsides are:</p>
<ul>
<li><p>costly, might 2x or 3x from original cost</p>
</li>
<li><p>I have no experience how to do administer this kind of activity in the future in case I need to do it by my self</p>
</li>
</ul>
<p>Or other option I do this myself and will take less cost and experiencing it to gain knowledge how to administer vehicle tax and registration number. The downsides are:</p>
<ul>
<li><p>High effort, driving back and forth to multiple cities</p>
</li>
<li><p>Longer time to complete, will took months</p>
</li>
</ul>
<p>Then I decided to do it by myself. Took almost 6 months to finally got my new registered number but it was worth my effort.</p>
<p>I still wondering is it possible to make this process more efficient in this country? I understand that the vehicle needs to be physically there to manually checked by the verifier to prevent fraudulence. But all the paperwork was so long. It need at least your half a day to do the workflow like registration, verify the vehicle, paying taxes, and finally like need comeback 1 or 2 months to get the final result. Don’t forget to came early in the morning so you will get early queue.</p>
<p>If you don’t have any flexible working time, definitely needs a day off to do that or at least a half day off. Or like I said previously, “<em>biro jas</em>a” is there to help you but it will costly 2-3x. No problem if you have budget on that but most people don’t.</p>
<p>That’s it, have a great day!</p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-6-lost-the-momentum">← Unemployed #6 - Lost the Momentum</a></p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-8-an-unexpected-offer?showSharer=true">Unemployed #8 - An Unexpected Offer →</a></p>
]]></content:encoded></item><item><title><![CDATA[Unemployed #6 - Lost the Momentum]]></title><description><![CDATA[Today was unproductive. After 2 days didn’t wrote the journal, looks like I lost my momentum to be productive. My initial purpose was journaling for weekday only. Weekend should spent with family or I could built something for #WeekendBuild series.

...]]></description><link>https://blog.didiktrisusanto.dev/unemployed-6-lost-the-momentum</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/unemployed-6-lost-the-momentum</guid><category><![CDATA[Career]]></category><category><![CDATA[journal]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Mon, 14 Oct 2024 13:58:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/1K9T5YiZ2WU/upload/39cc2119464cb50b98d75ba95148a51d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today was unproductive. After 2 days didn’t wrote the journal, looks like I lost my momentum to be productive. My initial purpose was journaling for weekday only. Weekend should spent with family or I could built something for #WeekendBuild series.</p>
<blockquote>
<p>Or is it typical of starting a Monday?</p>
</blockquote>
<p>However, I started a day with cutting my hair. Yes, I cut my hair by myself and I’ve been doing it since 2013. Definitely not as best as barber’s result but personally it’s quite satisfy me. After that I don’t really have spirit to do some works.</p>
<p>Several days ago I had been <a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-3-runway?showSharer=true">invited</a> as guest writer on <a target="_blank" href="https://coderlegion.com">coderlegion.com</a> and today I tried to do cross posting my new blog about <a target="_blank" href="https://blog.didiktrisusanto.dev/creating-restful-api-using-spring-boot-for-the-first-time-weekendbuild">Java Spring Boot</a> to their platform. Apparently after posting the article, it still need to be moderated or reviewed. Fortunately it published successfully. You can check this link <a target="_blank" href="https://coderlegion.com/424/creating-restful-api-using-spring-boot-for-the-first-time">https://coderlegion.com/424/creating-restful-api-using-spring-boot-for-the-first-time</a>.</p>
<p>I tried to work on my short URL project with digging on Go Template with goal creating landing page based on <code>htmx</code> and <code>Tailwindcss</code>. But my brain couldn’t able to think. I don’t know if it related to today’s weather was very hot (~38c) so my room was like a sauna.</p>
<h1 id="heading-good-news">Good News</h1>
<p>The good news was my ex coworker in consulting company offered me a 3-4 months fulltime project. He said the stack are <code>Go</code> and <code>Nestjs</code>. Sounds fun!</p>
<p>The rate is not really high but enough to cover my monthly expense and also the opportunity to dig dive with <code>Go</code> &amp; <code>Nestjs</code>, some stacks that I’ve been interested recently. I’ll take it as working and learning and the same time. I expected I will gain more expertise on those stacks.</p>
<p>Bismillah, wish me luck.</p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-5-good-friends">← Unemployed #5 - Good Friends</a></p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-7-the-effort-of-registering-vehicle-registration-number?showSharer=true">Unemployed #7 - The Effort of Registering Vehicle Registration Number →</a></p>
]]></content:encoded></item><item><title><![CDATA[Creating RESTful API Using Spring Boot for The First Time #WeekendBuild]]></title><description><![CDATA[Last time I wrote Java was early 2013 for my undergraduate thesis. It was a image processing program. I decided wrote in Java because during college era, Java was like main programming language that been taught. Also recently while job hunting, I saw...]]></description><link>https://blog.didiktrisusanto.dev/creating-restful-api-using-spring-boot-for-the-first-time-weekendbuild</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/creating-restful-api-using-spring-boot-for-the-first-time-weekendbuild</guid><category><![CDATA[Java]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[REST API]]></category><category><![CDATA[RESTful APIs]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Sun, 13 Oct 2024 12:44:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/fPkvU7RDmCo/upload/517ed8a39a429ddd0f7726966f777c13.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Last time I wrote Java was early 2013 for my undergraduate thesis. It was a image processing program. I decided wrote in Java because during college era, Java was like main programming language that been taught. Also recently while job hunting, I saw many jobs mentioned Spring Boot as their main stack, so I wonder why not try this stack for my <a target="_blank" href="https://blog.didiktrisusanto.dev/series/weekend-build">#WeekendBuild</a> series?</p>
<h1 id="heading-preparation">Preparation</h1>
<blockquote>
<p>I am using Windows machine (Windows 11) here so it could be Windows specific</p>
</blockquote>
<ul>
<li><p>Simply went to <a target="_blank" href="https://spring.io">spring.io</a> and check <a target="_blank" href="https://spring.io/quickstart">quick start page</a>.</p>
</li>
<li><p>Download and install JDK. I am using <a target="_blank" href="https://bell-sw.com/pages/downloads/#jdk-21-lts">JDK 21 LTS</a></p>
</li>
<li><p>Install Extension Pack for Java in VSCode</p>
</li>
<li><p>Once JDK installed, check Windows Environment Variable. Need to add <code>JAVA_HOME</code> path from installed JDK in user variable. Check in terminal or powershel with <code>java —version</code>. If showed up then ready to go.</p>
</li>
<li><p>Install Extension Pack for Java in VSCode. Then in <code>settings.json</code> add this config. If not, the package name would display a red mark although it’s fine.</p>
</li>
</ul>
<pre><code class="lang-json"><span class="hljs-string">"java.project.sourcePaths"</span>: [<span class="hljs-string">""</span>]
</code></pre>
<h1 id="heading-generate-spring-project">Generate Spring Project</h1>
<p>It’s good to know we don’t have initialize the project from scratch, we can use spring initializr.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728814322333/b14bb5a3-53f1-4a30-810f-af383f7c7e1d.png" alt="screenshot of spring initializr" class="image--center mx-auto" /></p>
<p>We can define the project attributes here. My setup:</p>
<ul>
<li><p>Gradle</p>
</li>
<li><p>Spring Boot default (3.3.4)</p>
</li>
<li><p>Metadata default (demo)</p>
</li>
<li><p>Packaging default (Jar)</p>
</li>
<li><p>Java 17</p>
</li>
<li><p><code>Spring Web</code> as dependencies because need to build RESTful API</p>
</li>
</ul>
<p><code>Generate</code> the code and later just extract the downloaded code.</p>
<p>I also noticed when it opened with VSCode and the extension pack already installed, Gradle will automatically download the dependencies in background so later we don’t have to do it manually.</p>
<h1 id="heading-first-web-service-api">First Web Service API</h1>
<p>The goal is I want to create a RESTful API that accept <code>GET /greeting</code> request and returned JSON response.</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"id"</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">"content"</span>: <span class="hljs-string">"Hello, World!"</span>
}
</code></pre>
<p>To do that, I need to create resource representation class.</p>
<blockquote>
<p>Spring uses <a target="_blank" href="https://github.com/FasterXML/jackson">https://github.com/FasterXML/jackson</a> that automatically marshal the class into JSON</p>
</blockquote>
<pre><code class="lang-java"><span class="hljs-comment">// /src/main/java/com/example/demo/Greeting.java</span>
<span class="hljs-keyword">package</span> com.example.demo;

<span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">Greeting</span><span class="hljs-params">(<span class="hljs-keyword">long</span> id, String content)</span> </span>{ }
</code></pre>
<p>Next, create a REST Controller to serve the request.</p>
<pre><code class="lang-java"><span class="hljs-comment">// /src/main/java/com/example/demo/GreetingController.java</span>
<span class="hljs-keyword">package</span> com.example.demo;

<span class="hljs-keyword">import</span> java.util.concurrent.atomic.AtomicLong;

<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RequestParam;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;

<span class="hljs-meta">@RestController</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GreetingController</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String template = <span class="hljs-string">"Hello %s!"</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> AtomicLong counter = <span class="hljs-keyword">new</span> AtomicLong();

    <span class="hljs-meta">@GetMapping("/greeting")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Greeting <span class="hljs-title">greeting</span><span class="hljs-params">(<span class="hljs-meta">@RequestParam(value = "name", defaultValue = "world")</span> String name)</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Greeting(counter.incrementAndGet(), String.format(template, name));
    }
}
</code></pre>
<ul>
<li><p><code>@RestController</code> annotation to identify that this controller will handle HTTP requests as part of RESTful service.</p>
</li>
<li><p><code>@GetMapping(“/greeting“)</code> annotation it’s like a route, to ensure HTTP request to <code>GET /greeting</code> will be handled by <code>Greeting</code> function.</p>
</li>
<li><p><code>@RequestParam()</code> annotation for binds the value of the query string parameter <code>name</code> into the <code>name</code> parameter of the <code>greeting()</code> method. In this case if the value empty, default value <code>world</code> will be used.</p>
</li>
<li><p>Finally it returned resource <code>Greeting</code> that we previously defined.</p>
</li>
</ul>
<h1 id="heading-running-the-service">Running The Service</h1>
<p>Go to Terminal and run this command to run the service</p>
<pre><code class="lang-bash">./gradlew bootRun
</code></pre>
<p>Or we can build the project and execute the <code>.jar</code> file</p>
<pre><code class="lang-bash">./gradlew build
java -jar build/libs/demo-0.0.1-SNAPSHOT.jar
</code></pre>
<p>My first attempt of running <code>bootRun</code> was failed. Later that was my mistake because my generated project was set to JDK 17 but I installed JDK 21 instead. Changing the language version in the <code>toolchain</code> was solved the problem.</p>
<pre><code class="lang-java">java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(<span class="hljs-number">21</span>)
    }
}
</code></pre>
<p>After the service is running, the API now is accessible at <code>http://localhost:8080/greeting</code></p>
<h1 id="heading-tests">Tests</h1>
<p>Lets digging more to the tests. It’s possible to test the controller using <code>MockMvc</code> package. In this case I want to test that <code>Greeting</code> controller is giving <code>200</code> HTTP Code and proper JSON format.</p>
<pre><code class="lang-java"><span class="hljs-comment">// src/tests/java/com/example/demo/GreetingControllerTest.java</span>
<span class="hljs-keyword">package</span> com.example.demo;

<span class="hljs-keyword">import</span> <span class="hljs-keyword">static</span> org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">static</span> org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

<span class="hljs-keyword">import</span> org.junit.jupiter.api.Test;
<span class="hljs-keyword">import</span> org.springframework.boot.test.context.SpringBootTest;
<span class="hljs-keyword">import</span> org.springframework.test.web.servlet.MockMvc;
<span class="hljs-keyword">import</span> org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
<span class="hljs-keyword">import</span> org.springframework.http.MediaType;
<span class="hljs-keyword">import</span> org.springframework.beans.factory.annotation.Autowired;
<span class="hljs-keyword">import</span> org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;

<span class="hljs-meta">@SpringBootTest</span>
<span class="hljs-meta">@AutoConfigureMockMvc</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GreetingControllerTest</span> </span>{
    <span class="hljs-meta">@Autowired</span>
    <span class="hljs-keyword">private</span> MockMvc mvc;

    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getGreeting</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
        mvc.perform(MockMvcRequestBuilders.get(<span class="hljs-string">"/greeting"</span>).accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath(<span class="hljs-string">"$.id"</span>).isNumber())
            .andExpect(jsonPath(<span class="hljs-string">"$.content"</span>).isString())
            .andExpect(jsonPath(<span class="hljs-string">"$.content"</span>).value(<span class="hljs-string">"Hello world!"</span>));
    }

    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getGreetingWithName</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
        mvc.perform(MockMvcRequestBuilders.get(<span class="hljs-string">"/greeting?name=johndoe"</span>).accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(jsonPath(<span class="hljs-string">"$.id"</span>).isNumber())
            .andExpect(jsonPath(<span class="hljs-string">"$.content"</span>).isString())
            .andExpect(jsonPath(<span class="hljs-string">"$.content"</span>).value(<span class="hljs-string">"Hello johndoe!"</span>));
    }
}
</code></pre>
<ul>
<li><p><code>@SpringBootTest</code> annotation tells Spring Boot to look for a main configuration class (one with <code>@SpringBootApplication</code>, for instance) and use that to start a Spring application context.</p>
</li>
<li><p><code>@AutoConfigureMockMvc</code> annotation to auto inject <code>MockMvc</code> as the test component. It useful to only test the function layer instead of running full server.</p>
</li>
<li><p><code>@Autowired</code> annotation is like a automatic dependency injection for <code>MockMvc</code> into the class test</p>
</li>
<li><p><code>@Test</code> annotation to tell Spring that the particular function is a test case. My first attempt I missed this annotation and when I ran the tests, the test case wouldn’t be detected.</p>
</li>
</ul>
<p>So by the code above you will know what the expectation of the test case, right?</p>
<p>Running the tests could be achieved at VSCode by navigate to <code>Testing</code> Icon on the left and click <code>Play</code> icon to run the tests.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728823026456/2b4411c2-308b-40b6-8671-497f5a75ffbb.png" alt="screenshot of running spring tests in VSCode" class="image--center mx-auto" /></p>
<p>Other approach is using command <code>./gradlew test</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728823094450/36de75c5-f776-4c88-93ac-ad54dba8f206.png" alt="screenshot of running spring test using gradlew" class="image--center mx-auto" /></p>
<h1 id="heading-code-repository">Code Repository</h1>
<p>I already published the full code on this repository <a target="_blank" href="https://github.com/didikz/my-first-spring">https://github.com/didikz/my-first-spring</a>.</p>
<p>Happy building, Folks!</p>
]]></content:encoded></item><item><title><![CDATA[Unemployed #5 - Good Friends]]></title><description><![CDATA[Believe it or not that having friends at your workplace is really good for your career (or maybe your life). I know sometimes you will prefer to limit your work-relationship for being just a “co-worker”. You come to work, interact with them, then jus...]]></description><link>https://blog.didiktrisusanto.dev/unemployed-5-good-friends</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/unemployed-5-good-friends</guid><category><![CDATA[Career]]></category><category><![CDATA[friendship]]></category><category><![CDATA[2Articles1Week]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Fri, 11 Oct 2024 15:00:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Cecb0_8Hx-o/upload/ca54592864c3677f6ef179a15cb9ba2b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Believe it or not that having friends at your workplace is really good for your career (or maybe your life). I know sometimes you will prefer to limit your work-relationship for being just a “co-worker”. You come to work, interact with them, then just straight go home. That’s also perfectly find. But if you could find someone who could be your real friend at work, then maybe you should.</p>
<p>Today I went to a cafe to meet some of my good friends from my old company. Even though we’re working in different company, but we’re often worked together in a cafe (yes we’re mostly a remote worker). They’re super fun, always full of funny stories, but also always supports each other. I relieved could tell the story why I took this decision and many things.</p>
<p>The funny thing is, these friends of mine are like 9-10 years younger than me. My wife often told me as a joke If I hangout with them is like I’ve been their older brother who took care my younger brother and sister.</p>
<p>This friendship started a long a go when they were intern at my old workplace during their vocation high school era or being a junior developer after high school graduation. So ya growing together from a company who has less than 10 employees to almost 50 for years made the good relationship. I’m like super proud knowing how they growth from a high school kid into a professional engineer who worked at good tech company.</p>
<blockquote>
<p>Sounds like a older brother, eh.</p>
</blockquote>
<p>Anyway, my team on Flip sent me a farewell gift which arrived today. It’s like a culture in Flip if your co-worker getting married, or new baby born, or even left the company, they will give them a gift. And they asked me what I want for the gift. Spent couple hours to think, but at the end I asked a Redmi Watch to support my <a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-4-riding-your-stress">riding hobby</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728658600685/2f2a73a0-6beb-4e47-abc4-f51421070841.jpeg" alt="an image of redmi watch 5 lite" class="image--center mx-auto" /></p>
<p>That’s beautiful. Thank you for your kindness, Gans (.</p>
<p>So, do you have good friends from your work? Would like to hear your story.</p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-4-riding-your-stress">←- Unemployed #4 - Riding Your Stress</a></p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-5-lost-the-momentum?showSharer=true">Unemployed #6 - Lost the Momentum →</a></p>
]]></content:encoded></item><item><title><![CDATA[Unemployed #4 - Riding Your Stress]]></title><description><![CDATA[Spent half a day riding my bike to Batu today. I think my only mistake was I started when the sun was already high so it was draining the energy so fast. Also the route is constantly climbing because Batu city is located in more higher elevation than...]]></description><link>https://blog.didiktrisusanto.dev/unemployed-4-riding-your-stress</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/unemployed-4-riding-your-stress</guid><category><![CDATA[2Articles1Week]]></category><category><![CDATA[Career]]></category><category><![CDATA[journal]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Thu, 10 Oct 2024 13:27:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1728566043230/ca8f9caf-f685-45e0-b25b-2be371bf1b9a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Spent half a day riding my bike to Batu today. I think my only mistake was I started when the sun was already high so it was draining the energy so fast. Also the route is constantly climbing because Batu city is located in more higher elevation than my house. So I took the challenge anyway.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728565149017/c3a0e3a5-8a86-44e8-a99c-7d32263d4e82.jpeg" alt="bicycle" class="image--center mx-auto" /></p>
<p>Even though I stopped many times to catch my breath, I finished it. Surprisingly didn’t feel much tired and felt my body more lighter!</p>
<p>Workout actually good for releasing stress. In my case is riding a bike because you can feel the air, sightseeing, or going to place where it’s hard to reach by car or motorcycle.</p>
<p>Didn’t much code today. Updating some SEO part on my website didiktrisusanto.dev based on <a target="_blank" href="https://chromewebstore.google.com/detail/website-seo-checker-free/nljcdkjpjnhlilgepggmmagnmebhadnk?hl=en">SEO checker extension</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728566251959/93c23d16-7306-494b-a1a3-95e5ba9c0b55.png" alt="SEO check screenshot" class="image--center mx-auto" /></p>
<p>Other than that, trying to following course on <a target="_blank" href="https://app.codecrafters.io/courses/redis/overview">codecrafters.io</a> for building your own Redis. This is interesting because I can learn about TCP connection and other system programming concept.</p>
<blockquote>
<p>Ah about job application…</p>
</blockquote>
<p>Nothing significant update. I applied to a remote job via <a target="_blank" href="https://bossjob.id/">bossjob</a> yesterday and this morning the recruiter asked my latest resume so I gave it via platform chat feature. However I also tried to submit another proposals at Upwork. 1 proposal viewed by client, that was my best progress on Upwork for now.</p>
<p>I think that’s it. Have a good day!</p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-3-runway">←- Unemployed #3 - Runway</a></p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-5-good-friends">Unemployed #5 - Good Friends —&gt;</a></p>
]]></content:encoded></item><item><title><![CDATA[Unemployed #3 - Runway]]></title><description><![CDATA[The Funds
Being unemployed mostly sucks right?

I know. Stop point at me, I took a long consideration before submitting my resignation letter

But at least to make it bearable, you have to consider these:

Your available funds

Your monthly expenses ...]]></description><link>https://blog.didiktrisusanto.dev/unemployed-3-runway</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/unemployed-3-runway</guid><category><![CDATA[Career]]></category><category><![CDATA[journal]]></category><category><![CDATA[Blogging]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Wed, 09 Oct 2024 05:31:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/xoU52jUVUXA/upload/e1d6e937fdfb40bf5e121cc1bc68e11c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-the-funds">The Funds</h1>
<p>Being unemployed mostly sucks right?</p>
<blockquote>
<p>I know. Stop point at me, I took a long consideration before submitting my resignation letter</p>
</blockquote>
<p>But at least to make it bearable, you have to consider these:</p>
<ul>
<li><p>Your available funds</p>
</li>
<li><p>Your monthly expenses (bills, loans, foods, insurance)</p>
</li>
<li><p>Your acceptable runway or how long you could live without a job in acceptable way</p>
</li>
</ul>
<p>First, I’m feeling grateful I no longer have loans or installments so it will make huge difference on my monthly expenses calculation. Secondly, my wife is still have a job so at least for basic needs it would be covered. Last, I am living in a city where the prices still considered not to expensive.</p>
<blockquote>
<p>Sorry my dear, I am “half” counting on you for now. Please be patient.</p>
</blockquote>
<p>Now back to runway.</p>
<p>Excluding my wife’s salary, I break down my monthly expense and cut down some of it if possible. Then I add some emergency funds as a “unemployed” budget. So based on that, I have at least 8 - 10 months of runway. I could stretched it to 11 - 12 months with adding some budget from my other saving but I wouldn’t prefer any over budget here so let stick with 8 - 9 months max.</p>
<p>So <strong>9 months</strong> will be my runway.</p>
<p>Hey I remember I possibly could take my funds on BPJS Ketenagakerjaan but I think I need to find reference about that. By looking the amount it probably could add 3 - 4 months additional runway.</p>
<blockquote>
<p>Why not starting business</p>
</blockquote>
<p>Bruh, I am pretty bad at this. But let see if I could find any interesting Idea for building digital business.</p>
<h2 id="heading-retirement-saving">Retirement Saving</h2>
<p>I’m having dilemma whether I keep add funds to my retirement saving or not while in this period. But first lets cut in half from original budget because I still need to prepare for retirement funds for my future.</p>
<p>If things go south, maybe will completely cut off the budget on this. We’ll see.</p>
<h1 id="heading-other-update">Other Update</h1>
<p>No update yet on job applications today. I submitted 2 proposals in Upwork today regarding Laravel related project but I have no big expectation on that.</p>
<p>This morning someone reached me at email offering me to become guest writer on their blog platform. This is interesting opportunity to sharpen my writing skill and potential to reaching more audience. But I have no information where’s the blogging platform. They only said it similar to dev.to. Already replied asking for the detail.</p>
<p>I just published a blog about using <a target="_blank" href="https://blog.didiktrisusanto.dev/how-to-use-golang-migrate-for-database-migration">Golang Migrate for manage database migration</a> based on my experiment yesterday. Probably today will continue playing around with my short URL application.</p>
<p>That’s it, have a great day!</p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-2-trying-productive">←- Unemployed #2 Trying Productive</a></p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-4-riding-your-stress">Unemployed #4 - Riding Your Stress —&gt;</a></p>
]]></content:encoded></item><item><title><![CDATA[How To Use Golang Migrate for Database Migration]]></title><description><![CDATA[It is common for web application having a database and using concept of migration already considered as best practice. Instead of creating database schema directly to database, migration script created so we could control how we manage the schema pro...]]></description><link>https://blog.didiktrisusanto.dev/how-to-use-golang-migrate-for-database-migration</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/how-to-use-golang-migrate-for-database-migration</guid><category><![CDATA[Go Language]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[migration]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Wed, 09 Oct 2024 03:55:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/fd9mIBluHkA/upload/f70dce18fa764bfc5e305ee16b0097e4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It is common for web application having a database and using concept of migration already considered as best practice. Instead of creating database schema directly to database, migration script created so we could control how we manage the schema programmatically and also would get benefit on CI/CD process.</p>
<p>There are several migration tools for Go but now let’s learn how to use <a target="_blank" href="https://github.com/golang-migrate/migrate">Golang Migrate</a> to manage migration in Go.</p>
<h1 id="heading-preparation">Preparation</h1>
<ul>
<li><p>Database server in this post, I am using PostgreSQL</p>
</li>
<li><p>For Windows, make sure it already installed <a target="_blank" href="https://scoop.sh/">Scoop</a></p>
</li>
<li><p>Go</p>
</li>
<li><p>Terminal</p>
</li>
</ul>
<h1 id="heading-cli-installation">CLI Installation</h1>
<p>Make sure Scoop already been installed and In your Windows terminal run this command to install <code>migrate</code></p>
<pre><code class="lang-bash">scoop install migrate
</code></pre>
<p>Once it done, run command <code>migrate</code> in Windows terminal. If everything is good, then it will indicated CLI correctly installed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728441397591/aea5f93f-2d22-436a-8d95-7c0a43f6ae6d.png" alt="image of migrate command" class="image--center mx-auto" /></p>
<blockquote>
<p>For other OS installation can be check on the instruction detail <a target="_blank" href="https://github.com/golang-migrate/migrate/tree/master/cmd/migrate">https://github.com/golang-migrate/migrate/tree/master/cmd/migrate</a></p>
</blockquote>
<h1 id="heading-create-migration">Create Migration</h1>
<p>We need a directory for storing the migration files. Create folder <code>migrations</code> in your Go project directory. You may create folder <code>database/migrations</code> or <code>db/migrations</code> depends on your preferred structure.</p>
<p>Go to your terminal and run the <code>migrate</code> command to start create migration files</p>
<pre><code class="lang-bash"> migrate create -ext sql -dir db/migrations -seq create_urls_table
</code></pre>
<ul>
<li><p><code>create</code> option is to create migration files</p>
</li>
<li><p><code>-ext sql</code> is to create migration files with <code>.sql</code> extension</p>
</li>
<li><p><code>-dir db/migrations</code> path for migrations file will be stored</p>
</li>
<li><p><code>-seq create_shortens_table</code> will generate up/down migrations for your table schema file sequentially</p>
</li>
</ul>
<blockquote>
<p>You may adjust the command and arguments based on your project structure</p>
</blockquote>
<p>Generated files should looks like this</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728444440465/60b894c3-e1c5-4694-8865-0a29485beed1.png" alt="image of generated migration files" class="image--center mx-auto" /></p>
<p>Now you just need to write SQL statement to create / altering / drop the table schema. Here’s sample of mine for PostgreSQL:</p>
<pre><code class="lang-sql">// 000001_create_urls_table.up.sql
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> urls(
    <span class="hljs-keyword">id</span> BIGSERIAL PRIMARY <span class="hljs-keyword">KEY</span>,
    slug <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">50</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> <span class="hljs-keyword">UNIQUE</span>,
    original_url <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">255</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
    user_id <span class="hljs-built_in">BIGINT</span> <span class="hljs-literal">NULL</span>,
    visit_count <span class="hljs-built_in">INT</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-number">0</span> <span class="hljs-literal">NULL</span>,
    created_at <span class="hljs-built_in">timestamp</span> <span class="hljs-keyword">DEFAULT</span> <span class="hljs-keyword">current_timestamp</span>,
    updated_at <span class="hljs-built_in">timestamp</span> <span class="hljs-literal">NULL</span>,
    deleted_at <span class="hljs-built_in">timestamp</span> <span class="hljs-literal">NULL</span>
);

// 000001_create_urls_table.down.sql
<span class="hljs-keyword">DROP</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">EXISTS</span> urls;
</code></pre>
<blockquote>
<p>To maintain idempotent, always check if the schema exists or not</p>
</blockquote>
<h1 id="heading-running-migration">Running Migration</h1>
<p>Migration files up/down already created meaning you will able to create the table or drop the table using recent migration files. Before running the migration, make sure you already setup the database because it will need database URL to run.</p>
<p>My URL for PostgreSQL is like below</p>
<pre><code class="lang-javascript">postgres:<span class="hljs-comment">//postgres:securepassword@localhost:5432/mydatabase?sslmode=disable</span>

<span class="hljs-comment">// convention</span>
driver:<span class="hljs-comment">//username:pass@host:port/dbname?sslmode=disable</span>
</code></pre>
<p>Then we can now run <code>up</code> migration to create new table schema</p>
<pre><code class="lang-bash">migrate -database postgres://postgres:securepassword@localhost:5432/mydatabase?sslmode=<span class="hljs-built_in">disable</span> -<span class="hljs-built_in">source</span> file://db/migrations up
</code></pre>
<ul>
<li><p><code>-database</code> tells migrate to use particular database server</p>
</li>
<li><p><code>-source</code> locates migration files path which is in <code>db/migrations</code> directory</p>
</li>
<li><p><code>up</code> tells migrate to run <code>000001_create_urls_table.up.sql</code></p>
</li>
</ul>
<p>When we need to revert the migration, we can use <code>down</code> option to drop the table schema</p>
<pre><code class="lang-bash">migrate -database postgres://postgres:securepassword@localhost:5432/mydatabase?sslmode=<span class="hljs-built_in">disable</span> -<span class="hljs-built_in">source</span> file://db/migrations down
</code></pre>
<blockquote>
<p>Other command can be referred to this <a target="_blank" href="https://github.com/golang-migrate/migrate/tree/master/cmd/migrate">https://github.com/golang-migrate/migrate/tree/master/cmd/migrate</a></p>
</blockquote>
<h1 id="heading-getting-database-value-from-yaml-file">Getting Database Value From YAML File</h1>
<p>Our project often uses config file to store the database configuration. In my case, it stored on YAML file <code>config.yml</code>. In <a target="_blank" href="https://github.com/golang-migrate/migrate/tree/master/cmd/migrate">Migrate’s Github</a> there’s example to do that.</p>
<p>First, install <code>Python</code> and <code>pip</code>. Then install <code>pyyaml</code> library for reading the <code>yaml</code> file.</p>
<pre><code class="lang-bash"> pip install pyyaml
</code></pre>
<p>Now we can modify our migration command to</p>
<pre><code class="lang-bash">migrate -database <span class="hljs-string">"<span class="hljs-subst">$(cat config.yml | python -c <span class="hljs-string">"import yaml,sys; print(yaml.safe_load(sys.stdin)['database'])"</span>)</span>"</span> -<span class="hljs-built_in">source</span> file://db/migrations up
</code></pre>
<p>It basically using same structure as previous command but now we read <code>yaml</code> file and parsing the <code>database</code> to get the URL value.</p>
<p>That’s it. Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Unemployed #2 - Trying Productive]]></title><description><![CDATA[Time to Bank Visit
I had plan to stay productive this day. No particular plan for what I wanted to build or learn yet in the morning. But first thing in the morning after dropped off my wife to office, I went straight to bank because I got troubled f...]]></description><link>https://blog.didiktrisusanto.dev/unemployed-2-trying-productive</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/unemployed-2-trying-productive</guid><category><![CDATA[Career]]></category><category><![CDATA[journal]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[job search]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Tue, 08 Oct 2024 13:53:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/ukzHlkoz1IE/upload/48874047af55a5fc3bd104fb95e1c09b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-time-to-bank-visit">Time to Bank Visit</h1>
<p>I had plan to stay productive this day. No particular plan for what I wanted to build or learn yet in the morning. But first thing in the morning after dropped off my wife to office, I went straight to bank because I got troubled for enabling their mobile application after changed my phone. This is become little bit urgent since my emergency fund is in that bank.</p>
<p>Turned out it was because my identity number registered in the bank already outdated, that’s why when I tried to input my identity number always got invalid error. 15 minutes later it sorted out and I was able to access the app again.</p>
<p>Drive straight back to home.</p>
<h1 id="heading-visiting-job-application-again">Visiting Job Application, Again</h1>
<p>Really becoming daily routine as unemployed person, eh?</p>
<p>Yesterday I followed up to a recruiter the rejection reason, no replied yet. Already talked to friend who works there, he was little bit surprised about the rejection. Although I didn’t have a clue about the reason, I just assumed I failed because in first assessment I said <code>NO</code> if I willing to relocate. It because my friend said there are several peoples are working remotely in this town for the company so I guess it was ok to said no. But again it was just assumption, let’s skip it for now and wait for the official response (I doubt it).</p>
<p>Also I sent a reply to one of my job application which been responded the night before. Again, stated that it is WFO policy and asked if I willing to relocate. I said no and also gave them a reason why I choose remote working without loosing productivity. Very understandable for me if they choose to reject me on this one. I wonder why I applied to this particular vacancy knowing it’s not remote position hahaha.</p>
<blockquote>
<p>Just got idea if I should write some like remote manifesto for my self where I explained why I prefer remote working</p>
</blockquote>
<p>There are update for several my applications either my application already viewed or in CV review process. Let’s wait for several days.</p>
<p>Next, I checked on Upwork proposal. Seems already hired a person to do the job. Move on.</p>
<h1 id="heading-working-on-short-url-project">Working on Short URL Project</h1>
<p>I have a fun project to build some kind of short URL application like bit.ly or other similar application. Built using Go and it’s still very raw, only covered index application and redirection function, no database integration yet. So I planned to integrate with PostgreSQL and DB migration tool today.</p>
<p>BTW, here’s the repository <a target="_blank" href="https://github.com/didikz/goshu">https://github.com/didikz/goshu</a></p>
<p>After prepared the database, I decided to use <a target="_blank" href="https://github.com/golang-migrate/migrate">Golang Migrate</a> as DB migration tool. Oh boy, spent couple hours to just make it usable and also integrated with PostgreSQL.</p>
<p>By afternoon I created the migration scripts successfully, query the data using <a target="_blank" href="https://github.com/jmoiron/sqlx">sqlx</a>, and redirecting the slug into the original URL. Will create a blog post for DB migration later.</p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-1-the-beginning">←- Unemployed #1 - The Beginning</a></p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-3-runway?showSharer=true">Unemployed #3 - Runway —&gt;</a></p>
]]></content:encoded></item><item><title><![CDATA[Unemployed #1 - The Beginning]]></title><description><![CDATA[So, my official last day at office was 30th Sept 2025 while I have new job to work with. But It’s okay, I already explain it about what I’ve done and what’s the next journey in my previous blog regarding the work update.
This is not my first time
Yup...]]></description><link>https://blog.didiktrisusanto.dev/unemployed-1-the-beginning</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/unemployed-1-the-beginning</guid><category><![CDATA[Career]]></category><category><![CDATA[journal]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Mon, 07 Oct 2024 10:59:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/5QgIuuBxKwM/upload/82dcecac287343e13ca7a27c43f45e40.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So, my official last day at office was 30th Sept 2025 while I have new job to work with. But It’s okay, I already explain it about what I’ve done and what’s the next journey in my previous blog regarding the <a target="_blank" href="https://blog.didiktrisusanto.dev/work-update-resigned">work update</a>.</p>
<h1 id="heading-this-is-not-my-first-time">This is not my first time</h1>
<p>Yup, this is not my first time I am being unemployed or living without a job. Back in 2014, my first company which I worked with was shutdown and getting trouble to pay our several months salary. Beside the office drama, my saving was less than 5 million but it was no big deal since I was living at my parent’s house. I even bought an electric guitar so I’m not bored at home.</p>
<p>It was 3 - 4 months without full time job. I did a freelance gig to do some project with my college friend. Earned enough to increase my saving. I also tried to open a little cafe with my local friends but then I decided to left because I enjoyed coding more and let my friends continue the business.</p>
<p>Back then in my home town, working at home or freelancing was unusual. People started looking me as a person who had many leisure time. And being at home all the time felt like a burden, so I started to looking a job. Finally I got it in small software company called <em>Ayowes!</em> in Malang.</p>
<h1 id="heading-preparing-the-laptop">Preparing the Laptop</h1>
<p>Acer Nitro is my only one daily laptop after I returned my work laptop to office. Last time I used for work was around 2022 before I joined Flip. After that it only being used for gaming and casual browsing. So today I decided to cleaning it and updating some development environment in it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728295829133/3fb28318-1150-455e-8804-8645e140256e.jpeg" alt="disassembled laptop for cleaning" class="image--center mx-auto" /></p>
<p>Cleaned dust in fans, re-pasta the processor and GPU made the laptop is more “relaxed” now. Previously the fans sounding like a plane taking off.</p>
<p>Checked PHP version still using version 8+, so it’s fine. Next, updating Go version because I want it to latest version (1.23.2). I tried to download the latest version and install from <a target="_blank" href="https://go.dev/dl/">official page</a>, it didn’t update the version automatically. <a target="_blank" href="https://stackoverflow.com/questions/43569617/how-do-i-update-golang-from-command-prompt-on-windows">Stackoverflow</a> suggested to use <a target="_blank" href="https://chocolatey.org/packages/golang">Chocolatey</a> instead,</p>
<pre><code class="lang-bash">choco upgrade golang -y
</code></pre>
<p>and it updated automatically.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728297353465/6fb7ab6b-6885-486a-a656-a7699b21cddc.png" alt="showing go version in terminal" class="image--center mx-auto" /></p>
<h1 id="heading-updating-some-online-portfolios">Updating Some Online Portfolios</h1>
<p>Because I no longer associated with my ex employers, I urgently need to update my online portfolios.</p>
<p>Updated my <a target="_blank" href="https://github.com/didikz">Github</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728297791049/1de7d0a4-dbce-459e-b44d-f506f77e6ef7.png" alt="github profile" class="image--center mx-auto" /></p>
<p>Updated personal site at <a target="_blank" href="https://didiktrisusanto.dev">didiktrisusanto.dev</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728297822579/2aeea79b-7445-4f3b-8d0d-8ba943df90bc.png" alt="Didik's personal site" class="image--center mx-auto" /></p>
<p>That’s it for now. I planned to enhance more for my personal site but still don’t have any idea how.</p>
<h1 id="heading-submit-first-upwork-proposal">Submit First Upwork Proposal</h1>
<p>I read it somewhere and my friend who actively doing some gig in Upwork said it hard to get a project. But hey, who knows I’m lucky? I started to choose the project and submit my first proposal to it.</p>
<p>I have no big expectation on it but let’s see.</p>
<h1 id="heading-following-up-recruiters">Following Up Recruiters</h1>
<p>I have several job prospects on my hands. This morning I got rejection email after first stage of hiring process from a tech company based on Yogyakarta. I have no clue what’s the rejection factor so I replied to ask for clarify which points that make me failed.</p>
<blockquote>
<p>Why I asked this? This is important for me because I would want to know why I failed so I can get better on the other hiring process.</p>
</blockquote>
<p>No response yet, but let’s see for couple days. If still no response? I’ll simply let it go.</p>
<p>Been following up other recruiter from Glints for a prospect at a social commerce company. Her last update was I made through the next step after first interview, but it was several weeks ago so I little bit worried. Fortunately there’s good news, still considered as passed to the next step and they will decide the schedule for next interviews.</p>
<p>Hopefully this is a good sign.</p>
<p><a target="_blank" href="https://blog.didiktrisusanto.dev/unemployed-2-trying-productive?showSharer=true">Unemployed #2 - Trying Productive —&gt;</a></p>
]]></content:encoded></item><item><title><![CDATA[Work Update: Resigned]]></title><description><![CDATA[So after an amazing 2 years and 7 months at Flip as backend engineer, I finally signed out. Actually almost no issues at work, but there are family circumstances that made me took hard decision to leave after new working arrangement that full working...]]></description><link>https://blog.didiktrisusanto.dev/work-update-resigned</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/work-update-resigned</guid><category><![CDATA[Career]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Mon, 30 Sep 2024 15:44:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/6dW3xyQvcYE/upload/271e08777b10c13c3ff60ca595da8f88.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So after an amazing 2 years and 7 months at Flip as backend engineer, I finally signed out. Actually almost no issues at work, but there are family circumstances that made me took hard decision to leave after new working arrangement that full working remotely is not an option anymore. Tried to negotiate but the result still no.</p>
<p>So I took this decision.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727708905886/9f6566e4-7199-42bf-b970-97c16c5833f3.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-what-ive-learned">What I’ve Learned</h1>
<p>Long time ago I dreamed building an amazing product and widely used by many peoples. Joining Flip was one of my best decision to achieve that goal. I didn’t really have many experience to build a SaaS product with a complex system and huge traffic before and at this company I’ve learned a lot how to do that.</p>
<p>Also learned a lot about</p>
<ul>
<li><p>Managing large legacy codebase yet still has quite good maintainability</p>
</li>
<li><p>Learned Go</p>
</li>
<li><p>Handling huge amount of traffic and billion value of transactions</p>
</li>
<li><p>Handling production incidents</p>
</li>
<li><p>Leading project or tech initiatives</p>
</li>
<li><p>Many good technical insights from great engineers</p>
</li>
<li><p>Making a good product that satisfies customers</p>
</li>
<li><p>Insight about running organization for almost hundreds engineers</p>
</li>
<li><p>Learned FE development (<em>just a little</em>)</p>
</li>
<li><p>Many more…</p>
</li>
</ul>
<h1 id="heading-the-legacy">The Legacy</h1>
<p>Couldn’t tell any detail but I made 20++ RFCs for tech initiatives that almost 90% implemented successfully in my team. Also released 200++ merge requests into our main repository.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727710140590/9abc99df-1e93-48c6-82e0-93bc6a63b98b.png" alt class="image--center mx-auto" /></p>
<p>Hopefully my team don’t blame me for the messy codes I wrote.</p>
<h1 id="heading-whats-next">What’s Next?</h1>
<p>So I am still looking for the new opportunity especially for remote working. There’re several prospects but let's see.</p>
<p>On the other side I want to take a break for a moment, refreshing my mind, re-learning many things to reshaping my current expertise. Although many years of experiences, many rejections would still affecting my confident levels. At this point I am questioning myself whether this is an effect of the tech winter or just skill issues of mine.</p>
<p>One of my best friend in college offered me to help him build a digital product which we believed there’s potential on that. Not sure how, but I do have a plan to discuss it with him.</p>
<p>I am thinking about <a target="_blank" href="https://www.upwork.com">Upwork</a> but looks very hard especially when you are new and have no reputation yet. Alternatively perhaps I will looking some coding gigs that could helped me grinding new tech. Not really fast pace but should be comfortable enough to gain experience safely while building portfolios.</p>
<p>That’s it, looking forward for the new chapter.</p>
<p>See you around, folks!</p>
]]></content:encoded></item><item><title><![CDATA[Simple Laravel Inertia React and Tailwindcss Starter Kit]]></title><description><![CDATA[It started when I want to refreshing my skill on front end development with building something related with React but I kind of didn't want to build API-based backend then I was remember Inertiajs exists to solve that. I did several projects with Lar...]]></description><link>https://blog.didiktrisusanto.dev/simple-laravel-inertia-react-and-tailwindcss-starter-kit</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/simple-laravel-inertia-react-and-tailwindcss-starter-kit</guid><category><![CDATA[Laravel]]></category><category><![CDATA[Inertia.js]]></category><category><![CDATA[React]]></category><category><![CDATA[vite]]></category><category><![CDATA[Tailwind CSS]]></category><category><![CDATA[starter-kit]]></category><category><![CDATA[PHP]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Mon, 02 Sep 2024 15:38:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/u2Ru4QBXA5Q/upload/bc7ae6b41e8f3ad4c222a79531b7ab4c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It started when I want to refreshing my skill on front end development with building something related with React but I kind of didn't want to build API-based backend then I was remember <a target="_blank" href="https://inertiajs.com">Inertiajs</a> exists to solve that. I did several projects with Laravel + Inertiajs several years ago so it must be easy to setup.</p>
<p>Surprisingly though, it was hard.</p>
<p>I know, seems skill issue or because didn't use it for years. But at the end, I made it to become a starter kit for future self or maybe you who need it.</p>
<p>Want to jump directly to the code? Here we go</p>
<p><a target="_blank" href="https://github.com/didikz/laravel-inertia-react-starter"><strong>https://github.com/didikz/laravel-inertia-react-starter</strong></a></p>
<h1 id="heading-stacks">Stacks</h1>
<p>I want to be specific with React so the main stacks are:</p>
<ul>
<li><p><a target="_blank" href="https://laravel.com">Laravel (v11)</a></p>
</li>
<li><p><a target="_blank" href="https://inertiajs.com">Inertiajs</a></p>
</li>
<li><p><a target="_blank" href="https://react.dev">React</a></p>
</li>
<li><p><a target="_blank" href="https://tailwindcss.com">Tailwindcss</a></p>
</li>
<li><p><a target="_blank" href="https://vitejs.dev">Vite</a></p>
</li>
</ul>
<p>Including suplementaries:</p>
<ul>
<li><p><a target="_blank" href="https://tailwindui.com">Tailwindui</a></p>
</li>
<li><p><a target="_blank" href="https://eslint.org">Eslint</a></p>
</li>
<li><p><a target="_blank" href="https://heroicons.com/">Heroicons</a></p>
</li>
</ul>
<h1 id="heading-learning-journey">Learning Journey</h1>
<p>Basically after I install Laravel, I was only following all the steps on the Inertiajs documentation regarding setup server side and setup client side for React, but it turned out like playing with puzzle to get fit one by one.</p>
<p>So here are the missing pieces in Vite and React setup. Assumed that inertiajs for server already been installed.</p>
<ol>
<li>Install React dependencies</li>
</ol>
<pre><code class="lang-bash">npm install react react-dom @inertiajs/react @vitejs/plugin-react
</code></pre>
<ol start="2">
<li><p>Change <code>.js</code> extension file to <code>.jsx</code></p>
</li>
<li><p>In <code>/resources/js/app.jsx</code> change <code>js</code> extention to <code>.jsx</code> in page resolver. So it will look like this</p>
<pre><code class="lang-javascript"> resolve: <span class="hljs-function"><span class="hljs-params">name</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> pages = <span class="hljs-keyword">import</span>.meta.glob(<span class="hljs-string">'./Pages/**/*.jsx'</span>, { <span class="hljs-attr">eager</span>: <span class="hljs-literal">true</span> });
    <span class="hljs-keyword">return</span> pages[<span class="hljs-string">`./Pages/<span class="hljs-subst">${name}</span>.jsx`</span>];
 },
</code></pre>
</li>
<li><p>Go to <code>vite.config.js</code> and add plugin for <code>react</code> and <code>laravel</code>. Don't forget use <code>.jsx</code> extension becase I already changed that in the first place.</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'vite'</span>;
 <span class="hljs-keyword">import</span> laravel <span class="hljs-keyword">from</span> <span class="hljs-string">'laravel-vite-plugin'</span>;
 <span class="hljs-keyword">import</span> react <span class="hljs-keyword">from</span> <span class="hljs-string">'@vitejs/plugin-react'</span>;

 <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
     <span class="hljs-attr">plugins</span>: [
         react(),
         laravel({
             <span class="hljs-attr">input</span>: [<span class="hljs-string">'resources/css/app.css'</span>, <span class="hljs-string">'resources/js/app.jsx'</span>],
             <span class="hljs-attr">refresh</span>: <span class="hljs-literal">true</span>,
         }),
     ],
 });
</code></pre>
</li>
<li><p>Create the Inertia root view file usually at <code>/views/app.blade.php</code> and include <code>@vite</code> directive. Once again change to <code>.jsx</code> extension on the js file.</p>
</li>
<li><p>It should be able to built and running the site but auto reload when I changed the script page it will got error.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725290774833/6ae9ec1a-017c-4c67-9730-25493e0d8231.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>The solution was simply add <code>@viteReactRefresh</code> directive before <code>@vite</code> in root Inertia file. It will inject some script that would allow us to hot reload when there is a change in our script.</p>
<pre><code class="lang-javascript"> @viteReactRefresh
 @vite([<span class="hljs-string">'resources/js/app.jsx'</span>, <span class="hljs-string">'resources/css/app.css'</span>])
 @inertiaHead
</code></pre>
</li>
<li><p>That's it, the rest only need to create <code>Pages</code> and ready to go.</p>
</li>
<li><p>Running the service would be need two separate terminals</p>
<pre><code class="lang-bash"> <span class="hljs-comment"># running the backend</span>
 php artisan serve

 <span class="hljs-comment"># Running the frontend</span>
 npm run dev
</code></pre>
</li>
</ol>
<p>Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Large CSV Processing Using Javascript #WeekendBuild]]></title><description><![CDATA[Previous series I already covered large csv processing using PHP and Go. Now in new #WeekendBuild series I will use Javascript to process large csv file. Same requirements: extract and parse the file, count total of rows, collect city data and how ma...]]></description><link>https://blog.didiktrisusanto.dev/large-csv-processing-using-javascript-weekendbuild</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/large-csv-processing-using-javascript-weekendbuild</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[data processing]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[learning]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Wed, 28 Aug 2024 15:00:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/LqKhnDzSF-8/upload/53fd9536851c31f5e1259ca08dc4cdce.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Previous series I already covered large csv processing using <a target="_blank" href="https://blog.didiktrisusanto.dev/large-csv-processing-using-php-weekendbuild">PHP</a> and <a target="_blank" href="https://blog.didiktrisusanto.dev/large-csv-processing-using-go-weekendbuild">Go</a>. Now in new <a target="_blank" href="https://blog.didiktrisusanto.dev/series/weekend-build">#WeekendBuild</a> series I will use Javascript to process large csv file. Same requirements: extract and parse the file, count total of rows, collect city data and how many customers on it, and finally sorted it by most customers.</p>
<p>If you want to jump out directly to the code, check this final result on my <a target="_blank" href="https://github.com/didikz/csv-processing/tree/main/javascript">Github Repository</a>.</p>
<h1 id="heading-load-amp-extract-data">Load &amp; Extract Data</h1>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">'fs'</span>;
<span class="hljs-keyword">const</span> reader = fs.createReadStream(path, { <span class="hljs-attr">encoding</span>: <span class="hljs-string">"utf8"</span> });
<span class="hljs-comment">// handle errors</span>
reader.on(<span class="hljs-string">'error'</span>, <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">`Error reading file: <span class="hljs-subst">${err}</span>`</span>));
<span class="hljs-comment">// chunk the data stream and concat it into a single variable</span>
<span class="hljs-keyword">let</span> data = <span class="hljs-string">''</span>;
reader.on(<span class="hljs-string">'data'</span>, <span class="hljs-function">(<span class="hljs-params">chunk</span>) =&gt;</span> data += chunk);
</code></pre>
<p>The idea was reading a huge file contents and could be done without having huge resource consumption. I've found <a target="_blank" href="https://nodejs.org/api/fs.html#filehandlecreatereadstreamoptions"><em>createReadStream</em></a> function which able to read a file into a readable stream and I could chunk the stream for parsing the data later. My stupid idea was load the chunked data and concat it into single string variable.</p>
<p>Let see how it goes.</p>
<h1 id="heading-parsing-amp-mapping-data">Parsing &amp; Mapping Data</h1>
<pre><code class="lang-javascript"><span class="hljs-comment">// at the end of the process, process the concatenated data and process it to map &amp; sorting</span>
reader.on(<span class="hljs-string">'end'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> customers = data.split(<span class="hljs-string">'\n'</span>)
                      .slice(<span class="hljs-number">1</span>) <span class="hljs-comment">// do not include header row</span>
                      .filter(<span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> row !== <span class="hljs-string">''</span>) <span class="hljs-comment">// do not include empty row</span>
                      .map(<span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> {
                         <span class="hljs-keyword">let</span> splitted = [];
                         <span class="hljs-keyword">let</span> current = <span class="hljs-string">''</span>;
                         <span class="hljs-keyword">let</span> inQuotes = <span class="hljs-literal">false</span>;
                         <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> char <span class="hljs-keyword">of</span> row) {
                            <span class="hljs-keyword">if</span> (char === <span class="hljs-string">'"'</span>) {
                                inQuotes = !inQuotes;
                            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (char === <span class="hljs-string">','</span> &amp;&amp; !inQuotes) {
                                 splitted.push(current);
                                 current = <span class="hljs-string">''</span>;
                            } <span class="hljs-keyword">else</span> {
                                 current += char;
                            }
                          }
                          splitted.push(current);
                          <span class="hljs-keyword">return</span> splitted;
                       });
});
</code></pre>
<p>By the end of the stream now I will able to parsing the string from loaded chunks and determine values in each rows. I know there is <a target="_blank" href="https://www.npmjs.com/package/csv-parse">csv-parse library</a> that can be used but The challenge is only use standard or internal lib to solve the requirements. The logic are:</p>
<ol>
<li><p>Split string by '\n' character</p>
</li>
<li><p>No need to parsing the header so skip it by using slice</p>
</li>
<li><p>No need to include empty row because it will counted as a row but has no data. Skip it using filter</p>
</li>
<li><p>Build customers array using map for parsing result</p>
</li>
</ol>
<p>Assumed each column in a row is separated by comma so it will be easy, but turned out there are string that wrapped in <em>double-quotes</em> has comma in it.</p>
<pre><code class="lang-javascript"><span class="hljs-number">7</span>,EA4d384DfDbBf77,Darren,Peck,<span class="hljs-string">"Lester, Woodard and Mitchell"</span>,Lake Ana,Pitcairn Islands,(<span class="hljs-number">496</span>)<span class="hljs-number">452</span><span class="hljs-number">-6181</span>x3291,+<span class="hljs-number">1</span><span class="hljs-number">-247</span><span class="hljs-number">-266</span><span class="hljs-number">-0963</span>x4995,tgates@cantrell.com,<span class="hljs-number">2021</span><span class="hljs-number">-08</span><span class="hljs-number">-24</span>,<span class="hljs-attr">https</span>:<span class="hljs-comment">//www.le.com/</span>
</code></pre>
<p>That unexpected data will ruin the parser so need additional logic to parser for detecting the string inside double-quotes.</p>
<p>Finally I already have customers array and I need to map it for city and customer count then sorted it by using <strong><em>sort</em></strong> function.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// map the data for city and total customers</span>
<span class="hljs-keyword">let</span> cities = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>(); <span class="hljs-comment">// 'city name': total customer</span>
customers.forEach(<span class="hljs-function">(<span class="hljs-params">val</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (cities.has(val[<span class="hljs-number">6</span>])) {
        cities.set(val[<span class="hljs-number">6</span>], cities.get(val[<span class="hljs-number">6</span>]) + <span class="hljs-number">1</span>);
    } <span class="hljs-keyword">else</span> {
        cities.set(val[<span class="hljs-number">6</span>], <span class="hljs-number">1</span>);
    }
});

<span class="hljs-comment">// sorted by the biggest customers first</span>
<span class="hljs-keyword">const</span> sorted = <span class="hljs-built_in">Array</span>.from(cities).sort(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> b[<span class="hljs-number">1</span>] - a[<span class="hljs-number">1</span>]);

<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'rows count: '</span> + customers.length);
<span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">JSON</span>.stringify(sorted));
</code></pre>
<h1 id="heading-first-result">First Result</h1>
<p>It did great job when load 100.000 rows but failed at 1 million rows. When processing 1 million rows I got error like this below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724854444752/278f1dae-5383-4830-9175-a2ac9751f97e.png" alt class="image--center mx-auto" /></p>
<p>I think the problem was when I concatenated all the loaded strings into a single variable caused that error. Stored a huge string into a memory is a bad idea.</p>
<h1 id="heading-improvement">Improvement</h1>
<p>Oke I need another approach and was thinking about async-await so I modified it into a function. Basically still using same <strong><em>createReadStream</em></strong> function but now I also tried using <strong><em>readline</em></strong> because it seems more proper way to read and iterate the data.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">'fs'</span>;
<span class="hljs-keyword">import</span> readline <span class="hljs-keyword">from</span> <span class="hljs-string">'readline'</span>;

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">processCsvAwait</span>(<span class="hljs-params">path</span>) </span>{
    <span class="hljs-comment">// track the time</span>
    <span class="hljs-built_in">console</span>.time(<span class="hljs-string">'processing_csv_time'</span>);

    <span class="hljs-keyword">const</span> reader = fs.createReadStream(path, { <span class="hljs-attr">encoding</span>: <span class="hljs-string">"utf8"</span> });
    <span class="hljs-keyword">const</span> rl = readline.createInterface({
        <span class="hljs-attr">input</span>: reader,
        <span class="hljs-attr">crlfDelay</span>: <span class="hljs-literal">Infinity</span>
    });
    <span class="hljs-comment">// the rest of codes</span>
}

processCsvAwait(<span class="hljs-string">'../data/customers-1000000.csv'</span>);
</code></pre>
<p>Then for extract and parsing the data should be like this below.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> count = <span class="hljs-number">0</span>;
<span class="hljs-keyword">let</span> cities = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
<span class="hljs-keyword">for</span> <span class="hljs-keyword">await</span> (<span class="hljs-keyword">const</span> line <span class="hljs-keyword">of</span> rl) {
   <span class="hljs-comment">// process each row, excluding header row</span>
   <span class="hljs-keyword">if</span> (count &gt; <span class="hljs-number">1</span>) { <span class="hljs-comment">// skip header row</span>
     <span class="hljs-comment">// logic parsing still same like first implementation   </span>
     <span class="hljs-comment">// ...    </span>
   }

   <span class="hljs-comment">// mapping cities logic still same like first implementation</span>
   <span class="hljs-comment">// ...</span>
   count++;
}
</code></pre>
<h1 id="heading-improvement-result">Improvement Result</h1>
<p>After using improved method finally I was able to processed 1 mil rows with <strong>~2 seconds</strong> time. Yeah that's a closed number with PHP result back then. Looks like still missing something for pushing the performance better but couldn't figure out where yet. Hopefully in the next <a target="_blank" href="https://blog.didiktrisusanto.dev/series/weekend-build">#WeekendBuild</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724856723069/0c4133fb-2b87-4299-a364-ab40880c4256.png" alt class="image--center mx-auto" /></p>
<p>That's it!</p>
]]></content:encoded></item><item><title><![CDATA[Large CSV Processing using PHP #WeekendBuild]]></title><description><![CDATA[Previously on my first #weekendbuild series, we already know how to process large CSV data in Go. Now as comparison, let's do the same thing use PHP. The idea and the output will be the same as first experiment in Go, no 3rd party library or dependen...]]></description><link>https://blog.didiktrisusanto.dev/large-csv-processing-using-php-weekendbuild</link><guid isPermaLink="true">https://blog.didiktrisusanto.dev/large-csv-processing-using-php-weekendbuild</guid><category><![CDATA[PHP]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Data Preprocessing]]></category><category><![CDATA[optimization]]></category><dc:creator><![CDATA[Didik Tri Susanto]]></dc:creator><pubDate>Sun, 04 Aug 2024 08:12:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Wpnoqo2plFA/upload/d12f125cf2cbc9980cd6176f08949503.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Previously on my first #weekendbuild series, we already know how to <a target="_blank" href="https://blog.didiktrisusanto.dev/large-csv-processing-using-go-weekendbuild">process large CSV data in Go</a>. Now as comparison, let's do the same thing use PHP. The idea and the output will be the same as first experiment in Go, no 3rd party library or dependencies will be used.</p>
<h1 id="heading-reading-csv-with-php">Reading CSV with PHP</h1>
<p>Same concept for processing the CSV, we will load or open the given file and parse the csv data. Fortunately, PHP has standard lib for this called <a target="_blank" href="https://www.php.net/manual/en/function.fgetcsv.php">fgetcsv</a> and we will use that function.</p>
<h2 id="heading-load-amp-extract-data">Load &amp; Extract Data</h2>
<p>Now opening the csv file in PHP is quite easy and can be achieved only using standard lib <a target="_blank" href="https://www.php.net/manual/en/function.fopen.php">fopen</a></p>
<pre><code class="lang-php">$f = fopen(<span class="hljs-string">"../data/customers-1000000.csv"</span>, <span class="hljs-string">"r"</span>);
<span class="hljs-keyword">if</span> ($f !== <span class="hljs-literal">false</span>) {
    <span class="hljs-comment">// process the csv</span>
} <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">echo</span> <span class="hljs-string">"Error opening file\n"</span>;
}
</code></pre>
<p><code>fopen</code> received a file path string and <code>"r"</code> argument is a "read only" mode since we only need to read the files. This function will return <a target="_blank" href="https://www.php.net/manual/en/language.types.resource.php">resources</a> when it successfully loaded or boolean <code>false</code> when it failed, so simple if else for error handling would do the job.</p>
<p>For extracting the rows, use <code>fgetcsv</code> and extract the row to an defined array to hold the value.</p>
<pre><code class="lang-php">    $records = [];
    <span class="hljs-keyword">while</span> (($csv = fgetcsv($file)) !== <span class="hljs-literal">false</span>) {
        $num = count($csv);
        $record = [];
        <span class="hljs-keyword">for</span> ($c=<span class="hljs-number">0</span>; $c &lt; $num; $c++) {
            $record[] = $csv[$c];
        }
        $records[] = $record;
    }
</code></pre>
<p>This would work but when I tried to load 1 million rows, it got out of memory error:</p>
<pre><code class="lang-bash">PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes)
</code></pre>
<p>Not surprised at all and It's kind of expected because php array could took many resources especially dealing with the large data. I checked <code>php.ini</code> it stated that current <code>memory_limit</code> setting was only <code>128M</code>.</p>
<p>Workaround on this is temporary increase the <code>memory_limit</code> on the runtime temporarily.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
ini_set(<span class="hljs-string">"memory_limit"</span>, <span class="hljs-string">"512MB"</span>)
</code></pre>
<p>So when we run the script, we also tell PHP to set memory limit to 512 MB before executing the rest of the process. Other way we could also change <code>"memory_limit"</code> in <code>php.ini</code> as for permanent change so all of php executions will be use that as default limit.</p>
<p>However this is not a good idea because increasing memory limit is indicating our process is not efficient and we have to avoid it.</p>
<h2 id="heading-generators">Generators</h2>
<p>Instead of building an array, <a target="_blank" href="https://www.php.net/manual/en/language.generators.overview.php">PHP generators</a> might be useful.</p>
<blockquote>
<p>A generator allows you to write code that uses foreach to iterate over a set of data without needing to build an array in memory, which may cause you to exceed a memory limit, or require a considerable amount of processing time to generate. Instead, you can write a generator function, which is the same as a normal function, except that instead of returning once, a generator can yield as many times as it needs to in order to provide the values to be iterated over.</p>
</blockquote>
<p>How we do that?</p>
<p>We can create a function to build the data using generators and called it later in our main function.</p>
<pre><code class="lang-php">$f = fopen(<span class="hljs-string">"../data/customers-1000000.csv"</span>, <span class="hljs-string">"r"</span>);
<span class="hljs-keyword">if</span> ($f !== <span class="hljs-literal">false</span>) {
    $records = extractCsv($f);
} <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">echo</span> <span class="hljs-string">"Error opening file\n"</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">extractCsv</span>(<span class="hljs-params">$file</span>) </span>{
    <span class="hljs-keyword">while</span> (($csv = fgetcsv($file)) !== <span class="hljs-literal">FALSE</span>) {
        $num = count($csv);
        $record = [];
        <span class="hljs-keyword">for</span> ($c=<span class="hljs-number">0</span>; $c &lt; $num; $c++) {
            $record[] = $csv[$c];
        }
        <span class="hljs-keyword">yield</span> $record;
    }
}
</code></pre>
<p>Thats it!</p>
<h2 id="heading-mapping-and-sorting-data">Mapping and Sorting Data</h2>
<p>Now since we're able to iterate the data we will mapping the customer data based on city and total customers. We could initiate an array to hold the map values, something like <code>cityMap["jakarta"] = 100</code></p>
<pre><code class="lang-php">$cityMap = [];
$rows = <span class="hljs-number">0</span>;
$records = extractCsv($f); <span class="hljs-comment">// extract csv records and load it to iterator</span>
<span class="hljs-keyword">foreach</span> ($records <span class="hljs-keyword">as</span> $key =&gt; $record) {
    $rows++;
    <span class="hljs-keyword">if</span> ($key == <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">continue</span>;  <span class="hljs-comment">// skip header row</span>
    }

    <span class="hljs-comment">// create array map for city and total customers</span>
    <span class="hljs-keyword">if</span> (array_key_exists($record[<span class="hljs-number">6</span>], $cityMap)) {
        $cityMap[$record[<span class="hljs-number">6</span>]]++;
    } <span class="hljs-keyword">else</span> {
        $cityMap[$record[<span class="hljs-number">6</span>]] = <span class="hljs-number">1</span>;
    }
 }
</code></pre>
<p>Previously in Go version we used bubble sort algorithm to manually sort the data from map, PHP has built in sort function called <a target="_blank" href="https://www.php.net/manual/en/function.arsort.php"><code>arsort</code></a> to sort an array value in descending order but still maintain the keys correlation.</p>
<pre><code class="lang-php">arsort($cityMap);
<span class="hljs-keyword">echo</span> <span class="hljs-string">"sorted from most customers in the city: "</span> . json_encode($cityMap) . PHP_EOL;
</code></pre>
<h2 id="heading-getting-memory-usage-and-processing-time">Getting Memory Usage and Processing Time</h2>
<p>We need to initiate the memory usage and a timer before executing the main process and later we calculate it at end of the process.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-comment">// start profiling</span>
$startMemory = memory_get_usage();
$timeStart = microtime(<span class="hljs-literal">true</span>); 

<span class="hljs-comment">// starting main processes</span>
<span class="hljs-comment">// ...</span>
<span class="hljs-comment">// end of the process</span>
$timeEnd = microtime(<span class="hljs-literal">true</span>);
$endMemory = memory_get_usage();

<span class="hljs-keyword">echo</span> sprintf(<span class="hljs-string">"processing time: %f (s)"</span>, ($timeEnd - $timeStart))  . PHP_EOL;
<span class="hljs-keyword">echo</span> sprintf(<span class="hljs-string">"memory usage: %f (Mb)"</span>, ($endMemory - $startMemory) / <span class="hljs-number">1024</span> / <span class="hljs-number">1024</span>)  . PHP_EOL;
</code></pre>
<h1 id="heading-the-result">The Result</h1>
<p>Running php application is simply to run with</p>
<pre><code class="lang-bash">php index.php
</code></pre>
<p>The result for loading and processing 1 million rows of csv was:</p>
<ul>
<li><p>Processing time: <strong>~2-3s</strong></p>
</li>
<li><p>Memory usage: <strong>0.030350 (Mb)</strong></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722758565482/fcc4e5df-bdad-42bc-88a9-7bcbf84e4b44.png" alt class="image--center mx-auto" /></p>
<p>Not bad. Performance wise, Go is better since it's a compiled language meanwhile PHP is not.</p>
<p>Full source code can be checked on my Github: <a target="_blank" href="https://github.com/didikz/csv-processing/tree/main/php">https://github.com/didikz/csv-processing/tree/main/php</a></p>
<h1 id="heading-lesson-learned">Lesson Learned</h1>
<p>Handling large data in PHP is quite tricky and we need to be aware about the memory usage especially when dealing with the arrays. We could increase <code>memory_limit</code> as "urgent" solution especially when there's an issue in production but <strong>don't do that</strong>. Optimize the application instead.</p>
]]></content:encoded></item></channel></rss>