<?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[Seth Phat Tech Blog]]></title><description><![CDATA[Technical sharing, journeys talking in Tech zone, Software Engineering by Seth Phat - in English version]]></description><link>https://sethphat.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 17 May 2026 08:41:10 GMT</lastBuildDate><atom:link href="https://sethphat.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Fetch and compute first, write later]]></title><description><![CDATA[Hey guys,
An update after I posted the “Laravel Excel: How to append rows to an existing Excel file“ a while back. This post will give you another solution and probably a better way to export that fits every case.
Problems
Previously, when trying to ...]]></description><link>https://sethphat.dev/fetch-and-compute-first-write-later</link><guid isPermaLink="true">https://sethphat.dev/fetch-and-compute-first-write-later</guid><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Tue, 16 Dec 2025 04:53:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/wC4Dae0-h5A/upload/65b7bbb17b5edd8401a0b25cd22a8b17.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>An update after I posted the <a target="_blank" href="https://sethphat.dev/laravel-excel-how-to-append-rows-to-an-existing-excel-file"><strong>“Laravel Excel: How to append rows to an existing Excel file“</strong></a> a while back. This post will give you another solution and probably a better way to export that fits every case.</p>
<h2 id="heading-problems">Problems</h2>
<p>Previously, when trying to export more than 30k records, the overall process was getting slow.</p>
<p>A diagram to show the previous implementation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1765859099864/a18e603b-409a-4b71-8295-08a02807e049.png" alt class="image--center mx-auto" /></p>
<p>The bottleneck was between the “Write to Excel”. Where we have to:</p>
<ul>
<li><p>Download the Excel file from S3</p>
</li>
<li><p>Write (append)</p>
</li>
<li><p>Upload to S3 again</p>
</li>
</ul>
<p>After 30k records, the file was huge (IIRC, 100~150MB). Each iteration took a lot of time and surpassed <code>$timeout = 600</code> lol.</p>
<p>Well, at least we spotted the bottleneck; let’s enhance it.</p>
<h2 id="heading-solutions">Solutions</h2>
<p>Every exporter out there, we always do:</p>
<ul>
<li><p>Fetch data</p>
</li>
<li><p>Compute data (transform into readable or accountable data)</p>
</li>
<li><p>Write</p>
</li>
</ul>
<p>Let’s add some love for fetch &amp; compute.</p>
<h3 id="heading-fetch-amp-compute">Fetch &amp; Compute</h3>
<p>Fetch &amp; compute are the most important tasks. We should handle it with care, indeed. So I created a new table:</p>
<ul>
<li><p>export_rows</p>
<ul>
<li><p>id</p>
</li>
<li><p>export_id</p>
</li>
<li><p>data (json column)</p>
</li>
</ul>
</li>
</ul>
<p>After fetching &amp; computing each record, we’ll write into the <code>export_rows</code> table. The <code>data</code> column will store an array of values, e.g. <code>['Seth', 'Vietnam', 'github.com/sethsandaru']</code></p>
<p>To ensure <code>fetch</code> runs fast, I believe you already know how to optimize your query and add enough indexes.</p>
<h3 id="heading-write">Write</h3>
<p>Once we insert all the <code>export_rows</code>. It’s time to write. We create a new Excel/CSV file and simply:</p>
<ul>
<li><p>Pull data by chunk</p>
<ul>
<li>Approx pulling 1k records would take around <strong>~150ms</strong> (even faster with nowadays CPU)</li>
</ul>
</li>
<li><p>Write it into the file</p>
<ul>
<li>Approx appending 1k records would take <strong>~200ms</strong> (even faster with nowadays CPU, too)</li>
</ul>
</li>
<li><p>Upload to S3</p>
</li>
</ul>
<p>This will take faster since it’s purely reading simple things from the DB, no hard pressure. Then write &amp; upload.</p>
<p>Using AWS Lambda, average processing time takes around ~20s for writing &amp; uploading 100k records, it’s pretty fast, I’d say.</p>
<p>Note: after uploading the exported file to S3, we should delete all of <code>export_rows</code> to save space.</p>
<h2 id="heading-result">Result</h2>
<p>From exporting in hours for thousands of records, it is now <strong>minutes</strong>. And of course, once it’s done, users will get notified via email.</p>
<p>Thanks for reading, and I hope it helps to improve your exporters!</p>
]]></content:encoded></item><item><title><![CDATA[Mark as default - the optimal way]]></title><description><![CDATA[Hey guys,
An easy topic for today! Make something a default (e.g., category, product, campaign, etc).
Let’s go with the categories table for the examples. Let’s say we only want one category to be the default one. And find out which is the optimal wa...]]></description><link>https://sethphat.dev/mark-as-default-the-optimal-way</link><guid isPermaLink="true">https://sethphat.dev/mark-as-default-the-optimal-way</guid><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Sat, 06 Dec 2025 10:18:18 GMT</pubDate><content:encoded><![CDATA[<p>Hey guys,</p>
<p>An easy topic for today! Make something a default (e.g., category, product, campaign, etc).</p>
<p>Let’s go with the <code>categories</code> table for the examples. Let’s say we only want one category to be the default one. And find out which is the optimal way to deal with.</p>
<h2 id="heading-the-easiest-way">The easiest way</h2>
<p>Usually, we would create a new column:</p>
<ul>
<li><p><code>is_default</code>: simplest column <code>0</code> for <code>false</code> and <code>1</code> for <code>true</code></p>
</li>
<li><p>or a nullable <code>defaulted_at</code> (timestamp/datetime) column.</p>
</li>
</ul>
<p>Upon marking a category as default, we’ll do two things:</p>
<ul>
<li><p>Mark all categories as not default (<code>UPDATE categories SET is_default = 0;</code>)</p>
</li>
<li><p>Mark the selected category as the default category (<code>UPDATE categories SET is_default = 1 WHERE id = xxx</code>)</p>
</li>
</ul>
<p>Upon fetching, we only need to include a simple condition (<code>SELECT … WHERE is_default = 1</code>)</p>
<h3 id="heading-why-it-isnt-optimal">Why it isn’t optimal?</h3>
<ul>
<li><p>We add a new column that <strong>99% of the records</strong> are storing either <code>NULL</code> or <code>0</code> =&gt; unnecessary data stored.</p>
</li>
<li><p>We <strong>always have to update many records</strong> when marking a category as the default.</p>
</li>
</ul>
<h2 id="heading-the-optimal-way">The optimal way</h2>
<p>We create a new table <code>default_categories (category_id int PRIMARY KEY)</code>.</p>
<p>Upon marking a category as default, we’ll do two things:</p>
<ul>
<li><p><code>DELETE FROM default_categories</code> // only one record</p>
</li>
<li><p><code>INSERT INTO default_categories(category_id) VALUES (xxx)</code></p>
</li>
</ul>
<p>Upon fetching, a simple where exists condition (join is ok, too)</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span>
...
<span class="hljs-keyword">FROM</span> categories c
<span class="hljs-keyword">WHERE</span>
    <span class="hljs-keyword">EXISTS</span> (<span class="hljs-keyword">SELECT</span> dc.id <span class="hljs-keyword">FROM</span> default_categories dc <span class="hljs-keyword">WHERE</span> dc.category_id = c.id)
</code></pre>
<p>With this, we don’t have to store unnecessary data, and we keep things super simple from both the database &amp; application layers.</p>
<h3 id="heading-going-forward">Going forward</h3>
<p>Honestly, for applications nowadays, a user can be a part of multiple teams. So our <code>default_categories</code> might look like this:</p>
<ul>
<li><p>default_categories</p>
<ul>
<li><p>id (int primary)</p>
</li>
<li><p>team_id (FK to <code>teams.id</code>, UNIQUE)</p>
</li>
<li><p>category_id (FK to <code>categories.id</code>)</p>
</li>
</ul>
</li>
<li><p>(Note: foreign keys are optional; if you want extra integrity, add them)</p>
</li>
</ul>
<p>Upon marking a category as default, it’s simpler, a single <code>UPSERT</code> (or <code>updateOrCreate</code> in Laravel):</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> default_categories (team_id, category_id)
<span class="hljs-keyword">VALUES</span> (:team_id, :category_id)
<span class="hljs-keyword">ON</span> <span class="hljs-keyword">DUPLICATE</span> <span class="hljs-keyword">KEY</span> <span class="hljs-keyword">UPDATE</span> category_id = :category_id;
</code></pre>
<h3 id="heading-notes">Notes</h3>
<p>This does not limit you to adding only one record as a default. Thinking <code>default_categories</code> of a pivot - <code>many-to-many</code> relationship table. Adjust it to match your needs.</p>
<p>We can have many default records, too!</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Well, what do you think?</p>
]]></content:encoded></item><item><title><![CDATA[Almost 2026, but I still choose WordPress for CMS]]></title><description><![CDATA[Hey guys,
Probably a chapter 2 of this https://sethphat.dev/why-is-wordpress-still-an-awesome-cms blog post, haha.
Here was the story: I’ve been spending like 2 days figuring out which CMS I want to use for my new blog (for my new hobby lol)
Let’s se...]]></description><link>https://sethphat.dev/almost-2026-but-i-still-choose-wordpress-for-cms</link><guid isPermaLink="true">https://sethphat.dev/almost-2026-but-i-still-choose-wordpress-for-cms</guid><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Sun, 30 Nov 2025 04:30:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/sSPzmL7fpWc/upload/e3e661be476b62b86445973599c42f46.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>Probably a chapter 2 of this <a target="_blank" href="https://sethphat.dev/why-is-wordpress-still-an-awesome-cms">https://sethphat.dev/why-is-wordpress-still-an-awesome-cms</a> blog post, haha.</p>
<p>Here was the story: I’ve been spending like 2 days figuring out which CMS I want to use for my new blog (for my new hobby lol)</p>
<p>Let’s see, I’ve considered these options, and why I don’t want to use them:</p>
<ul>
<li><p><strong>October CMS</strong>: no built-in blogging system, we have to install a blog plugin, and the main editor is a markdown editor (man, it’s almost 2026, I don’t want to write raw markdown lol).</p>
</li>
<li><p><strong>Filament</strong>: same as October CMS, we have to build or install a plugin for blogging. Their Rich Editor looks ok, though.</p>
</li>
<li><p><strong>Strapi</strong>: looks incredible, but I have to run a NodeJS app, which means another thing to observe/take care of, ok, not so cool.</p>
</li>
<li><p><strong>Custom build</strong>: I’ve been doing that for the last two sites, and it’s not cool, haha.</p>
<ul>
<li><p>I mean, if we want to learn how to build a CMS site, go for it.</p>
</li>
<li><p>But if we want <strong>peace</strong> and want to <strong>deploy and use</strong>, then don’t do it. Honestly, it looks overkill and a waste of huge time (and come up with something not so perfect to use, while others already did that for us based on their years of experience)</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-why-wordpress">Why WordPress?</h2>
<p><strong>First</strong>, as a person who has been working with PHP for 9 years, I feel comfortable using WordPress. It leads to:</p>
<ul>
<li><p>I can <strong>confidently</strong> build a theme</p>
</li>
<li><p>I can <strong>confidently</strong> build a plugin (if I want to)</p>
</li>
</ul>
<p><strong>Second</strong>, with the LLMs (I’ve only been using GPT &amp; Copilot), it helps me build and learn things around WordPress super fast.</p>
<ul>
<li><p>I believe when you’re at the Senior level, you know exactly what you want. You can let the LLMs generate code, then we review/adjust, and we have what we want.</p>
</li>
<li><p>And of course, learning, too!</p>
</li>
</ul>
<p><strong>Third</strong>, deploying PHP apps is super simple (I mean, LAMP stack is still a thing, and probably strong forever, haha). So here are the options:</p>
<ul>
<li><p>Laravel Forge (why not lol)</p>
</li>
<li><p>Bref to deploy on AWS Lambda</p>
</li>
<li><p>$5 VPS at Vultr, I can either run WordPress in:</p>
<ul>
<li><p>LAMP mode</p>
</li>
<li><p>High perf mode by utilizing RoadRunner/FrankenPHP</p>
</li>
</ul>
</li>
</ul>
<p><strong>Fourth</strong>, WordPress makes SEO easier. Additionally, we can also install a free plugin for extra SEO stuff (sitemap, meta, social media, etc)</p>
<p><strong>Last</strong>, WordPress is <strong>FREE</strong> and <strong>open source,</strong> has a <strong>large community,</strong> receives frequent updates, and is well-documented.</p>
<h3 id="heading-will-i-use-wordpress-as-an-admin-panel">Will I use WordPress as an Admin Panel?</h3>
<p>Probably not, lol!</p>
<p>For business apps, I’d go with Filament, which makes more sense (also easier)</p>
<h3 id="heading-bonus">Bonus</h3>
<p>I’ve found this gem <strong>_tw (</strong><a target="_blank" href="https://underscoretw.com/">https://underscoretw.com/</a>), which helps us to build a WordPress theme using Tailwind v4, super cool!</p>
<h2 id="heading-thank-you">Thank you!</h2>
<p>Alright, thanks for reading. It has been so long since my last post, haha! Will try to write more.</p>
]]></content:encoded></item><item><title><![CDATA[Laravel Excel: How to append rows to an existing Excel file]]></title><description><![CDATA[Hey guys,
Well damn, it has been a while since my last post lol, lazy & busy. But here I am, back to write useful tips for y’all 🥹
Let us do some exporting tasks for today, using Laravel Excel to export XLSX (yeah CSV is so 2010 lol). But, append mo...]]></description><link>https://sethphat.dev/laravel-excel-how-to-append-rows-to-an-existing-excel-file</link><guid isPermaLink="true">https://sethphat.dev/laravel-excel-how-to-append-rows-to-an-existing-excel-file</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Sat, 08 Mar 2025 06:52:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/GauA0hiEwDk/upload/8027c5b25c1e168afa8ad8af201eb1d4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>Well damn, it has been a while since my last post lol, lazy &amp; busy. But here I am, back to write useful tips for y’all 🥹</p>
<p>Let us do some exporting tasks for today, using Laravel Excel to export XLSX (yeah CSV is so 2010 lol). But, append more rows to an existing file.</p>
<h2 id="heading-the-approach">The approach</h2>
<p>From your export class (e.g.: <code>UserExport</code>), implements the <code>WithEvents</code></p>
<p>Once you added the <code>registerEvents</code> method, add this</p>
<pre><code class="lang-php">    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">registerEvents</span>(<span class="hljs-params"></span>): <span class="hljs-title">array</span>
    </span>{
        <span class="hljs-keyword">return</span> [
            BeforeWriting::class =&gt; <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">BeforeWriting $event</span>) </span>{
                <span class="hljs-keyword">if</span> (!Storage::exists(<span class="hljs-keyword">$this</span>-&gt;filePath)) {
                    <span class="hljs-keyword">return</span>;
                }

                file_put_contents(
                    $tempFile = tempnam(<span class="hljs-string">'/tmp'</span>, <span class="hljs-string">'export'</span>) . <span class="hljs-string">'.xlsx'</span>,
                    Storage::get(<span class="hljs-keyword">$this</span>-&gt;filePath)
                );

                $templateFile = <span class="hljs-keyword">new</span> LocalTemporaryFile($tempFile);

                $event-&gt;writer-&gt;reopen($templateFile, Excel::XLSX);
                $event-&gt;writer-&gt;getSheetByIndex(<span class="hljs-number">0</span>)
                    -&gt;export($event-&gt;getConcernable());

                <span class="hljs-keyword">return</span> $event-&gt;writer-&gt;getSheetByIndex(<span class="hljs-number">0</span>);
            },
        ];
    }
</code></pre>
<p>It will retrieve the file from your desired storage (local, public, s3, etc), store to <code>tmp</code> folder (which is available for both server &amp; serverless environment), and tell Laravel Excel to use that file to write more rows.</p>
<p>Simple right?</p>
<p>And yeah, I know, it’s a shame that reading an existing file is not available in the package.</p>
<h2 id="heading-after-this-what-do-we-get">After this, what do we get?</h2>
<h3 id="heading-daisy-chaining-jobs-to-export-tons-of-rows">Daisy-chaining jobs to export tons of rows</h3>
<p>Yep, each job can write around 500 ~ 1000 rows, then store the file, and dispatch another job to continue until it writes all rows.</p>
<p>Reduce memory-leak or timeout issues, since jobs only handle a small amount of data.</p>
<h3 id="heading-interact-with-s3-with-ease">Interact with S3 with ease</h3>
<p>Thanks to <code>Storage</code> from Laravel, this can be done super easy.</p>
<p>With the code above, I’ve read the file, and to write, simply:</p>
<pre><code class="lang-php">Excel::store(
    <span class="hljs-keyword">new</span> UserExport(collect($users), <span class="hljs-keyword">$this</span>-&gt;filePath),
    <span class="hljs-keyword">$this</span>-&gt;filePath
);
</code></pre>
<h3 id="heading-works-for-both-server-amp-serverless">Works for both Server &amp; Serverless</h3>
<p>Yep, tested on both server &amp; serverless (Lambda), lovely!</p>
<h2 id="heading-finally">Finally</h2>
<p>Thanks for reading and have fun!</p>
]]></content:encoded></item><item><title><![CDATA[Laravel Herd PRO reviews]]></title><description><![CDATA[Hey guys,
Recently, I just bought Herd PRO (a yearly subscription). I’ve been using the basic Herd since the first release.
So, my latest work required interaction with S3 storage, there are several ways to achieve that in the local env:

Use Cloud (...]]></description><link>https://sethphat.dev/laravel-herd-pro-reviews</link><guid isPermaLink="true">https://sethphat.dev/laravel-herd-pro-reviews</guid><category><![CDATA[Laravel]]></category><category><![CDATA[PHP]]></category><category><![CDATA[laravel herd]]></category><category><![CDATA[herd]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Wed, 18 Sep 2024 06:30:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1726641006493/2ecd9037-1d13-4772-9f41-5dba960559c6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>Recently, I just bought <a target="_blank" href="https://herd.laravel.com/">Herd PRO</a> (a yearly subscription). I’ve been using the basic Herd since the first release.</p>
<p>So, my latest work required interaction with <strong>S3 storage</strong>, there are several ways to achieve that in the local env:</p>
<ul>
<li><p>Use Cloud (S3-compatible e.g. DigitalOcean, Vultr or use AWS’s S3 directly)</p>
</li>
<li><p><a target="_blank" href="https://min.io/">MinIO</a>: S3-compatible storage, offer a self-hosted version</p>
<ul>
<li>I pick this</li>
</ul>
</li>
</ul>
<p>MinIO can be downloaded &amp; run in several ways: Docker, Brew, etc.</p>
<p>I found out that <strong>Herd is supporting MinIO</strong> out-of-the-box (for the PRO version), that was the time I made the purchase decision).</p>
<p>So let’s check it out, to see the PRO features of Herd PRO.</p>
<h2 id="heading-herd-pro-features">Herd PRO features</h2>
<h3 id="heading-services">Services</h3>
<p>I love this, helps us to create and manage a lot of services/dependencies:</p>
<ul>
<li><p>Database: MySQL, PgSQL, MongoDB</p>
</li>
<li><p>Cache: Redis</p>
</li>
<li><p>Queue: Redis</p>
</li>
<li><p>Broadcasting: Reverb</p>
</li>
<li><p>Search: MeiliSearch, Typesense</p>
</li>
<li><p>Storage: MinIO</p>
</li>
</ul>
<p>For instance, I created a MinIO instance, Herd gave me the instructions &amp; ENVs (all I needed to do was copy &amp; paste)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726635415792/28c89e7f-8a00-4260-b4d9-c9a4d3e19552.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-mail">Mail</h3>
<p>There are plenty of ways to test email locally, but now it’s getting way easier with Herd.</p>
<p>Typically, I’d use Mailtrap, but now, I’ll stick with Herd’s Mail.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726635537945/d13f189d-68e3-4e97-b3e3-5f1270b3a942.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-dumps">Dumps</h3>
<p><code>Dumps</code> is amazing, unlike <code>ray</code> , it requires us to install a dependency (or dev-dependency), <code>dumps</code> doesn’t require additional dependencies.</p>
<p>We can use either <code>dump</code> or <code>dd</code> function to write the logs. It also writes the DB queries &amp; Application Logs (<code>logger</code>).</p>
<h3 id="heading-debugger">Debugger</h3>
<p>If you like to debug using breakpoint, Herd PRO provides an easy integration, glued between PHPStorm and XDebug.</p>
<p>Honestly, I’m not a fan of this so I won’t give you guys any reviews here.</p>
<h2 id="heading-overall-reviews">Overall Reviews</h2>
<p>I really like Herd PRO, my overall local development has been optimized a bit:</p>
<ul>
<li><p>PHP (versions, update, etc) =&gt; Herd PRO</p>
</li>
<li><p>Database =&gt; Herd PRO (goodbye DBNgin 🥲)</p>
<ul>
<li>Optimized part here</li>
</ul>
</li>
<li><p>Cache =&gt; Herd PRO</p>
</li>
<li><p>External services: Herd PRO</p>
</li>
</ul>
<p>My overall source: <strong>4.5/5.</strong></p>
<p>To get 5/5, Herd PRO should be able to provide us these:</p>
<ul>
<li><p>Ability to install extra PHP extensions. At the moment, <a target="_blank" href="https://herd.laravel.com/docs/1/advanced-usage/php-extensions#installing-php-extensions"><strong>installing a specific extension</strong></a> is not so cool, way too much hassle.</p>
<ul>
<li>If somehow <code>brew</code> bumps the extension’s version, we have to update the <code>php.ini</code> again, I don’t like this.</li>
</ul>
</li>
<li><p>Ability to run Laravel Queue Worker</p>
</li>
<li><p>Ability to set up Sites with custom domains or ports.</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Well, as a person who loves to keep my local environment as minimal as possible, I do really love Laravel Herd. I can set up everything under the same Herd application.</p>
<p>If your applications are going to use more than a normal database, I’d recommend using Herd PRO. If not, let’s just stick with Herd Basic &amp; DBNgin (Free).</p>
<p>Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Digging the new Concurrency Facade from Laravel & the Early Review]]></title><description><![CDATA[Hey guys,
From LaraconUS 2024, Taylor shared with us a lot of cool stuff. From the code, and the framework to the infrastructure. PHP and Laravel are getting hot more than ever 🔥
A new Facade has been shared & released in the latest version: the Con...]]></description><link>https://sethphat.dev/digging-the-new-concurrency-facade-from-laravel-the-early-review</link><guid isPermaLink="true">https://sethphat.dev/digging-the-new-concurrency-facade-from-laravel-the-early-review</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[concurrency]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Sun, 15 Sep 2024 07:22:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1726384217046/290be727-479f-4aac-a3df-04679eed1c5f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>From <strong>LaraconUS 2024,</strong> Taylor shared with us a lot of cool stuff. From the code, and the framework to the infrastructure. PHP and Laravel are getting hot more than ever 🔥</p>
<p>A new Facade has been shared &amp; released in the latest version: the <code>Concurrency</code></p>
<p>Let’s check it out and I’ll share my early review on that.</p>
<h2 id="heading-the-concurrency-facade">The Concurrency Facade</h2>
<h3 id="heading-introduction">Introduction</h3>
<p>The <code>Concurrency</code> facade helps us to run tasks <strong>concurrently</strong>.</p>
<ul>
<li><p>Before, tasks need to be run <strong>synchronously</strong>.</p>
</li>
<li><p>After <code>laravel/framework</code> <a target="_blank" href="https://github.com/laravel/framework/releases/tag/v11.23.2">v11.23.2</a>, with <code>Concurrency</code>, we can run the tasks <strong>asynchronously*</strong></p>
<ul>
<li>learn more about the <strong>*</strong> in the <strong>Digging Deeper</strong> section below.</li>
</ul>
</li>
</ul>
<p>With that, we can dispatch and run our tasks (instead of one by one) and we <strong><em>can</em></strong> have</p>
<h3 id="heading-usage">Usage</h3>
<pre><code class="lang-php"><span class="hljs-comment">// $res is a Collection&lt;Result&gt;</span>
$res = Concurrency::run([
    <span class="hljs-function"><span class="hljs-keyword">fn</span> (<span class="hljs-params"></span>) =&gt; <span class="hljs-title">MyTask</span>::<span class="hljs-title">run</span>(<span class="hljs-params"></span>),
    <span class="hljs-title">fn</span> (<span class="hljs-params"></span>) =&gt; <span class="hljs-title">MyOtherTask</span>::<span class="hljs-title">run</span>(<span class="hljs-params"></span>), 
])</span>;

$res[<span class="hljs-number">0</span>] <span class="hljs-comment">// access the result</span>
</code></pre>
<h2 id="heading-digging-deeper-into-the-concurrency-facade">Digging deeper into the Concurrency Facade</h2>
<p>Let’s check out the implementation.</p>
<p>At first glance, there are 2 drivers that we can use:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/laravel/framework/blob/11.x/src/Illuminate/Concurrency/ProcessDriver.php">ProcessDriver</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/laravel/framework/blob/11.x/src/Illuminate/Concurrency/ForkDriver.php">ForkDriver</a> (uses <code>spatie/fork</code>)</p>
</li>
</ul>
<p>Since I always have a thing for the <code>in-house</code> stuff, I’ll only review <code>ProcessDriver</code> for this blog posting.</p>
<h3 id="heading-how-does-the-processdriver-work">How does the ProcessDriver work?</h3>
<p>Looking at the <code>run</code> driver, it will <strong>serialize our closure</strong> and <strong>send it to a new Console command</strong> that Laravel introduced.</p>
<p>That command will <strong>invoke the closure</strong> and return the result.</p>
<p>So we can see, <strong>1 concurrent task = 1 console command process</strong>.</p>
<h3 id="heading-considerations-problems-flaws">Considerations / Problems / Flaws</h3>
<ul>
<li><p>Each task process will <strong>bootstrap a whole application again</strong>.</p>
<ul>
<li>This could <strong>increase</strong> the number of DB connections.</li>
</ul>
</li>
<li><p>Having many processes would <strong>decrease</strong> the overall <strong>server's performance.</strong></p>
<ul>
<li>Probably worse in the serverless environment (AWS Lambda)</li>
</ul>
</li>
<li><p>It <em>might</em> have some <strong>out-of-context issues</strong> if the concurrent task has <strong>dependencies</strong> (e.g.: using <code>$this-&gt;something</code> reference)</p>
</li>
<li><p>It uses the <code>Promise.all</code> approach instead of the <code>Promise.allSettled</code>.</p>
<ul>
<li>So if there is <strong><em>an error</em></strong>, we won't get all the successful results (get an <code>Exception</code> instead)</li>
</ul>
</li>
</ul>
<h3 id="heading-reviews">Reviews</h3>
<h4 id="heading-small-tasks">Small tasks</h4>
<p>It’s not probably a good idea to use <code>Concurrency</code> for <strong>many small tasks</strong>, reasons:</p>
<ul>
<li><p>It has to bootstrap the whole application for each task, the overall bootstrap can take from <strong>10~70ms</strong> depending on the server’s gig &amp; codebase’s size.</p>
</li>
<li><p>Many processes <strong>will slow down</strong> the overall server’s performance.</p>
</li>
<li><p>There is no way to get successful results if there is an error in the middle. Perhaps this is something Laravel will need to improve soon.</p>
</li>
</ul>
<p>I was thinking of doing something like:</p>
<pre><code class="lang-php">Concurrency::run(
    $users-&gt;map(
        <span class="hljs-function"><span class="hljs-keyword">fn</span> (<span class="hljs-params">User $user</span>) =&gt; <span class="hljs-title">fn</span> (<span class="hljs-params"></span>) =&gt; <span class="hljs-title">SendWelcomeEmailJob</span>::<span class="hljs-title">dispatch</span>(<span class="hljs-params">$user</span>)
    )
)</span>;
</code></pre>
<p>But after digging a bit, I’m still sticking with the normal <code>foreach</code> and <code>dispatch</code> 🥹 It’s way more optimal for the time being.</p>
<h4 id="heading-large-tasks">Large Tasks</h4>
<p>If we can <strong>manage</strong> <strong>how</strong> <strong>many tasks we’re going to dispatch</strong>. Perhaps we can use Concurrency for large tasks (e.g.: concurrent 3rd party API calls).</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Well, guys, I’m still sticking with normal Laravel stuff and Queue Jobs at the time being hehe. Things are too early for the very best, let’s wait for a while for the improvements from Laravel’s Core Team and the lovely huge Laravel Community.</p>
<p>Currently, PHP is not a language designed to <strong><em>run things concurrently</em></strong> in a process. Most of the things are workaround solutions.</p>
<ul>
<li>The same goes for <code>fork</code> from Spatie, creating processes to run tasks.</li>
</ul>
<p>If you are using <code>Octane</code> x <code>Swoole</code>, then you can use the concurrency from Swoole which utilizes the <strong>Event Loop</strong> (the brain of JS), totally better.</p>
<p>Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Why do I avoid using AI for blogging]]></title><description><![CDATA[Hey guys,
Non-tech talk today hehe.
Generative AI has been trending for more than a year now. GPT-3 started the trend, and after that, there are many players joined the game.
I did use some generated AI content back in the day when HashNode injected ...]]></description><link>https://sethphat.dev/why-do-i-avoid-using-ai-for-blogging</link><guid isPermaLink="true">https://sethphat.dev/why-do-i-avoid-using-ai-for-blogging</guid><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Fri, 30 Aug 2024 07:30:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/RdmLSJR-tq8/upload/f753ed413be465a1e37abb84cacb28b3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>Non-tech talk today hehe.</p>
<p>Generative AI has been trending for more than a year now. GPT-3 started the trend, and after that, there are many players joined the game.</p>
<p>I did use some generated AI content back in the day when HashNode injected Claude (IIRC) into the platform.</p>
<p>At first, everything looks quite cool &amp; quick. However, after several articles, I stopped using AI, and I'll share my thoughts here 😉</p>
<h2 id="heading-the-thoughts">The Thoughts</h2>
<h3 id="heading-bad-content">Bad Content</h3>
<p>As a reader, you'd expect to read an article that gives you all the good details from personal real-life experience, hands-on practice, etc. Generative AI will always give you <strong>vague</strong> content.</p>
<p>Don't be so lazy. If you don't write, don't do it. If you write, <strong>do it properly, with heart.</strong></p>
<p>Once people like your content, they will follow your blog (absolutely).</p>
<h3 id="heading-using-too-much-academic-vocabulary">Using too much academic vocabulary</h3>
<p>English isn't my first language, so, I don't want to create an article that separates the readers.</p>
<p>I want <strong>everyone</strong> (even somebody who just knows basic English) to understand what I shared in my articles.</p>
<p>Too much academic stuff will make my articles unreachable.</p>
<h3 id="heading-bad-for-seo">Bad For SEO</h3>
<p>Google already warns us about this. And I agree with Google, why do I have to read a generated article when I can do a simple chat with GPT?</p>
<h3 id="heading-ugly-feature-image">Ugly Feature Image</h3>
<p>Honestly, I haven't used any picture generator, especially for my blog.</p>
<p>A blog where people design their own Feature Images (even if it's just a simple text and a simple background) is <strong>100% worth</strong> more than a generated AI image.</p>
<p>People think that generated AI will replace designers, won't be anytime soon (at least in the next 5~8 years). Human-to-human communication is already hard enough to conclude a good design, how come AI can understand us quicker &amp; better?</p>
<h2 id="heading-when-do-i-use-ai">When do I use AI?</h2>
<ul>
<li><p>To check the grammar (yeah English isn't my first language)</p>
</li>
<li><p>To search for something quickly</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Don't abuse AI, let's make blogging enjoyable.</p>
<p>Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Create a fancy input for your Laravel Commands]]></title><description><![CDATA[Hey guys,
Let's do some hands-on practice today. Gonna show you how to create a fancy input for your command.
Life is too short to do boring things, isn't it? Hehe.
The Fancy and the normal Inputs
When using $this->ask('Give me your freaking input'),...]]></description><link>https://sethphat.dev/create-a-fancy-input-for-your-laravel-commands</link><guid isPermaLink="true">https://sethphat.dev/create-a-fancy-input-for-your-laravel-commands</guid><category><![CDATA[Laravel]]></category><category><![CDATA[PHP]]></category><category><![CDATA[command line]]></category><category><![CDATA[cli]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Sat, 10 Aug 2024 04:08:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1723262950920/d6099a24-b51d-408b-83e8-2ef4c490bb49.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>Let's do some hands-on practice today. Gonna show you how to create a fancy input for your command.</p>
<p>Life is too short to do boring things, isn't it? Hehe.</p>
<h2 id="heading-the-fancy-and-the-normal-inputs">The Fancy and the normal Inputs</h2>
<p>When using <code>$this-&gt;ask('Give me your freaking input')</code>, it will show up an input like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723260431371/5379052a-0392-4318-b38a-c4ef398c191a.png" alt class="image--center mx-auto" /></p>
<p>That's a normal one, Laravel doesn't document the way to create a fancy input, like when we run the <code>make:model</code> without any arguments:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723260525844/603030ce-548b-4a87-a552-e570ec6ce5ee.png" alt class="image--center mx-auto" /></p>
<p>(sorry, can't take a picture via <code>ray.so</code>, copy things there and it's break haha)</p>
<p>As we can see, the fancy input has a proper border (which is nice), a placeholder text for examples.</p>
<p>On the other hand, if we press the cancel command (Mac: Command + C | Linux: Control + C), we'll see this message:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723260641470/2f150738-2d09-4e46-8663-155016c72c77.png" alt class="image--center mx-auto" /></p>
<p>And the normal <code>ask</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723260731552/79dcd13a-19b2-4668-a84f-b9859d5e46b2.png" alt class="image--center mx-auto" /></p>
<p>It's definitely way better than the normal one, right?</p>
<h2 id="heading-how-do-you-implement-the-fancy-inputs-for-laravel-command">How do you implement the Fancy Inputs for Laravel Command?</h2>
<p>There are some adjustments we need to make. Let's roll.</p>
<h3 id="heading-implements-promptsformissinginput">Implements <code>PromptsForMissingInput</code></h3>
<p>So that Laravel can smell the missing required arguments thus showing up the inputs for us 😉</p>
<h3 id="heading-use-name-not-signature">Use <code>$name</code>, not <code>$signature</code></h3>
<p>Instead of using <code>$signature</code>, we'll need to use <code>$name</code> (<strong>pure</strong> <strong>form</strong> <strong>without any options or arguments</strong> defined)</p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyFancyCommand</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Command</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">PromptsForMissingInput</span>
</span>{
    <span class="hljs-keyword">protected</span> $name = <span class="hljs-string">'app:handle-my-fancy-command'</span>;
}
</code></pre>
<h3 id="heading-define-inputs">Define Inputs</h3>
<p>We'll need to override the <code>getArguments</code> and the <code>promptForMissingArgumentsUsing</code> methods:</p>
<pre><code class="lang-php"><span class="hljs-comment">// define argument(s) of your command</span>
<span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getArguments</span>(<span class="hljs-params"></span>): <span class="hljs-title">array</span>
</span>{
    <span class="hljs-keyword">return</span> [
        [<span class="hljs-string">'name'</span>, InputArgument::REQUIRED, <span class="hljs-string">'Full Name'</span>],
    ];
}

<span class="hljs-comment">// define the input's label &amp; placeholder text</span>
<span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">promptForMissingArgumentsUsing</span>(<span class="hljs-params"></span>): <span class="hljs-title">array</span>
</span>{
    <span class="hljs-keyword">return</span> [
        <span class="hljs-string">'name'</span> =&gt; [
            <span class="hljs-string">'Can you plz give me your full name?'</span>,
            <span class="hljs-string">'E.g.: Seth Chen'</span>,
        ],
    ];
}
</code></pre>
<h3 id="heading-optional-define-your-optional-parameters">(Optional) Define your Optional parameter(s)</h3>
<p>Simply override the <code>getOptions</code> method:</p>
<pre><code class="lang-php"><span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getOptions</span>(<span class="hljs-params"></span>)
</span>{
    <span class="hljs-keyword">return</span> [
        [
            <span class="hljs-string">'admin'</span>, <span class="hljs-comment">// full-form: --admin</span>
            <span class="hljs-string">'a'</span>,  <span class="hljs-comment">// short-form: -a</span>
            InputOption::VALUE_NONE, <span class="hljs-comment">// boolean, no input</span>
            <span class="hljs-string">'Make this user as an Administrator'</span>, <span class="hljs-comment">// description</span>
        ],
    ];
}
</code></pre>
<p>For <code>InputOption</code>, take the reference here:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723261993673/dc4c32e6-c760-40ad-86f1-ffdbd07270cf.png" alt class="image--center mx-auto" /></p>
<p>(yes I love light mode 😈)</p>
<h3 id="heading-testing">Testing</h3>
<p>Now, let's hit the <code>app:handle-my-fancy-command</code> and try it out:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723262542799/49c81e12-bce8-4605-b913-0049a713612f.png" alt class="image--center mx-auto" /></p>
<p>Really awesome, isn't it?</p>
<h2 id="heading-what-if-my-terminal-cant-show-up-inputs">What if my terminal can't show up inputs?</h2>
<p>This is a fair question, we can't expect all machines (especially servers) to show up fancy things.</p>
<p>Simply use <strong>normal arguments or optional parameters</strong> without any issue 😎.</p>
<h2 id="heading-final-words">Final Words</h2>
<p>From the way we <strong>define</strong> the <strong>arguments</strong> &amp; <strong>optional</strong> <strong>parameters</strong>, I feel like this is a <strong>better way</strong> for Laravel commands.</p>
<p>Putting too much text inside the <code>$signature</code> aren't so cool.</p>
<p>Enjoy and happy weekend! 😎</p>
]]></content:encoded></item><item><title><![CDATA[Why is WordPress still an awesome CMS?]]></title><description><![CDATA[Hey guys,
I want to talk about WordPress (WP) for today. It is one of the famous CMS platforms that has been around for decades.
Nowadays, people still consider WP as their go-to CMS for their websites, blogs, showcases, etc.
Let's check out why WP i...]]></description><link>https://sethphat.dev/why-is-wordpress-still-an-awesome-cms</link><guid isPermaLink="true">https://sethphat.dev/why-is-wordpress-still-an-awesome-cms</guid><category><![CDATA[PHP]]></category><category><![CDATA[Blogging]]></category><category><![CDATA[WordPress]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Fri, 26 Jul 2024 07:24:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1721976406096/ad442c88-2484-4edb-a6e6-64b15de61fbe.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>I want to talk about WordPress (WP) for today. It is one of the famous CMS platforms that has been around for decades.</p>
<p>Nowadays, people still consider WP as their go-to CMS for their websites, blogs, showcases, etc.</p>
<p>Let's check out why WP is so cool and awesome.</p>
<h2 id="heading-first-the-inspiration">First, the Inspiration</h2>
<p>The inspiration for this post is based on the newsletter from <strong>Nevo David</strong> 😆</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721567775492/ad1339e6-e0fe-4d84-a195-cb0cda68c3d6.png" alt class="image--center mx-auto" /></p>
<p>And recently, I've been deployed 2 new sites using WP (after 6 years). A lot of things have changed (in a good way).</p>
<h2 id="heading-why-wordpress">Why WordPress?</h2>
<h3 id="heading-top-notch-cms-with-decades-of-experience">Top-notch CMS with decades of experience</h3>
<p>You know what they said: the older, the wiser.</p>
<p>The same goes for WP, with years of experience, WP is still going strong &amp; better every day.</p>
<h3 id="heading-easy-installation-with-not-so-many-dependencies">Easy installation with not-so-many dependencies</h3>
<p>That's right, WordPress installation has been super straightforward since then.</p>
<p>There are only a few steps:</p>
<ul>
<li><p>Download the source code, unzip it</p>
</li>
<li><p>Setup PHP, MySQL &amp; Apache/Nginx</p>
</li>
<li><p>Change the DB config in <code>wp-config.php</code></p>
</li>
</ul>
<p>Then we can visit our website and finish the installation.</p>
<p>Well, if you compare WP with other CMS platforms, the installation isn't that simple.</p>
<h3 id="heading-state-of-the-art-editor-gutenberg">State-of-the-art Editor: Gutenberg</h3>
<p><img src="https://i0.wp.com/raw.githubusercontent.com/WordPress/gutenberg/trunk/docs/assets/overview-block-editor-2023.png?ssl=1" alt="Block Editor Handbook | Developer.WordPress.org" /></p>
<p>In order to write a good post, a good editor is required. In the past, the previous editor of WP was amazing.</p>
<p>But then they released Gutenberg in Dec 2018, which is more powerful. The customization, the looks, the integration, etc. Everything works so well.</p>
<h3 id="heading-search-engine-optimized-seo">Search Engine Optimized (SEO)</h3>
<p>Simply install <a target="_blank" href="https://yoast.com/wordpress/plugins/seo/">Yoast SEO plugin</a>, the FREE version has everything that we need (of course if you're a hardcore SEO engineer, go for the PRO version)</p>
<p>Yoast will give us a lot of helpful SEO recommendations, keyword tracking, etc.</p>
<h3 id="heading-flexibility">Flexibility</h3>
<p>WP isn't just built to be a simple blog. It can be your landing pages, home websites, e-commerce, resumé, etc.</p>
<p>You can check out a lot of awesome plugins and make your life easier.</p>
<p>Nowadays, people really care about performance (otherwise PageSpeed would bite us), so most plugins are well-optimized.</p>
<h3 id="heading-a-lovely-media-manager">A lovely Media Manager</h3>
<p>This is also a must-have for those who need to insert &amp; manage many media files (images, videos).</p>
<h3 id="heading-api-ready-headless-cms-opportunities">API-ready / Headless CMS opportunities</h3>
<p>WP exposes APIs for you so that you can use WP as a headless CMS and build your own front-end website.</p>
<p>Pretty handy for those who don't want to do frontend stuff in PHP (and JS) together.</p>
<h3 id="heading-optional-monetization-opportunities">(Optional) Monetization-opportunities</h3>
<p>How?</p>
<ul>
<li><p>Sell your themes</p>
</li>
<li><p>Sell your plugins (one-off or subscription)</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Honestly, everything has pros and cons, that's the nature of software engineering. But I do see more PROs from WP: easy setup, big community, SEO optimization, and a great editor.</p>
<h3 id="heading-price-cheap">Price: Cheap</h3>
<p>We can run WordPress on cheap shared hosting, or if you don't like shared hosting, buy a VPS from any service. The starting price would be around <strong>3 USD/month</strong>.</p>
<p>My recommendation: <a target="_blank" href="https://www.vultr.com/?ref=8912582">use Vultr</a>. It's cheap, reliable, and simple to set up everything.</p>
<p>I'm using 10+ Linux VPS on Vultr and totally love it.</p>
<h3 id="heading-performance-fast">Performance: Fast+</h3>
<p><strong>PHP (8.3+)</strong> is blazing fast nowadays, thus boosting WordPress up to a brand new level.</p>
<p>Want a higher <strong>next level</strong>? Check these out:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/StephenMiracle/frankenwp">FrankenWP</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/WordPress-PSR/swoole">WordPress x Swoole</a></p>
</li>
</ul>
<p>They will run WP in a <strong>long-running process</strong> (same as Node.JS or Go), which reduces the bootstrap time &amp; database connection =&gt; ridiculously faster.</p>
<h3 id="heading-why-not-use-wordpress-for-this-blog">Why not use WordPress for this blog?</h3>
<p>You might ask why I'm using HashNode. Fair enough, these are the reasons behind:</p>
<ul>
<li><p>I was <strong>too</strong> <strong>lazy 🥲</strong> to:</p>
<ul>
<li><p>Find or make a theme</p>
</li>
<li><p>Deploy a new site</p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>I wanted to create a simple blog (as simple as possible) just to write notes, and share knowledge.</p>
<ul>
<li>Lazy to follow best SEO practices too 😆.</li>
</ul>
</li>
</ul>
<h3 id="heading-will-i-ever-migrate-to-wp">Will I ever migrate to WP?</h3>
<p>Never say never, right? 😉</p>
<h3 id="heading-final">Final</h3>
<p>Thanks for reading 😉. See you in the next article.</p>
]]></content:encoded></item><item><title><![CDATA[Let's create a DataCache layer for your Laravel apps]]></title><description><![CDATA[Hey guys,
We all know that Cache facade plays a good role across the Laravel application lifecycle, isn't it?
Cache simply boosts up the requests by retaining the computed data from file storage or memory (aka RAM) depending on our configuration.
Tod...]]></description><link>https://sethphat.dev/lets-create-a-datacache-layer-for-your-laravel-apps</link><guid isPermaLink="true">https://sethphat.dev/lets-create-a-datacache-layer-for-your-laravel-apps</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[cache]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Sun, 07 Jul 2024 03:00:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720321108283/e9dd7b94-21ba-4204-8fb1-37dca04dd351.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>We all know that <code>Cache</code> facade plays a good role across the Laravel application lifecycle, isn't it?</p>
<p>Cache simply <strong>boosts up</strong> the requests by retaining the computed data from file storage or memory (aka RAM) depending on our configuration.</p>
<p>Today I'm gonna show you an awesome way to maintain your cache flow.</p>
<h2 id="heading-the-problems">The Problems</h2>
<p>Not only for other facades but also <code>Cache</code> provide us with a really simple way to use things (the beauty of Laravel - simple yet elegant)</p>
<p>However, it's a <em>double-edged sword</em>. We code things and we see it's simple, no biggie.</p>
<p>But by the nature of software development, from time to time, the codebase increases, and maintaining becomes harder.</p>
<p>Then people start to blame Laravel. It's not Laravel's fault, right? Laravel doesn't do anything, Laravel allows us to use any design patterns, and any approaches out there.</p>
<p>So let's get back to the main topic. We usually use <code>cache</code> like this:</p>
<pre><code class="lang-php"><span class="hljs-comment">// for example, Country is an entity that we don't change much</span>
<span class="hljs-comment">// CountryController.php</span>
<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">index</span>(<span class="hljs-params"></span>): <span class="hljs-title">JsonResponse</span>
</span>{
    $countries = Cache::rememberForever(
        <span class="hljs-string">'countries-list'</span>,
        <span class="hljs-function"><span class="hljs-keyword">fn</span> (<span class="hljs-params"></span>) =&gt; <span class="hljs-title">Country</span>::<span class="hljs-title">query</span>(<span class="hljs-params"></span>)-&gt;<span class="hljs-title">pluck</span>(<span class="hljs-params"><span class="hljs-string">'name'</span>, <span class="hljs-string">'code'</span></span>),
    )</span>;

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JsonResponse(compact(<span class="hljs-string">'countries'</span>));
}

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">store</span>(<span class="hljs-params">CountryStoreRequest $request</span>): <span class="hljs-title">JsonResponse</span>
</span>{
    $country = Country::create($request-&gt;validated());

    Cache::forget(<span class="hljs-string">'countries-list'</span>);

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JsonResponse();
}

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span>(<span class="hljs-params">CountryUpdateRequest $request, Country $country</span>): <span class="hljs-title">JsonResponse</span>
</span>{
    $country-&gt;update($request-&gt;validated());

    Cache::forget(<span class="hljs-string">'countries-list'</span>);

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JsonResponse();
}

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">destroy</span>(<span class="hljs-params">CountryDestroyRequest $request, Country $country</span>)): <span class="hljs-title">JsonResponse</span>
</span>{
    $country-&gt;delete();

    Cache::forget(<span class="hljs-string">'countries-list'</span>);

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JsonResponse();
}
</code></pre>
<p>The problems:</p>
<ul>
<li><p>Hardcoded cache key across the codebase, which can easily <strong>leak to human error</strong> when developing features.</p>
</li>
<li><p>The literal <code>'countries-list'</code> could be anything in the codebase, imagine a big codebase and you want to search for the cache stuff only, it's a nightmare.</p>
</li>
</ul>
<p>And that's just for a <em>fixed cache key</em>, imagine you want to cache for specific users, or businesses, etc. The complex just becomes bigger.</p>
<h2 id="heading-the-solution">The solution</h2>
<p>From the statement above:</p>
<blockquote>
<p>Laravel allows us to use any design patterns, and any approaches out there.</p>
</blockquote>
<p>Yes, don't limit our implementation, let's add more spicy stuff into the codebase. Let's go above and beyond ✈️</p>
<p>I'll create an AbstractDataCache with some simple implementations:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">DataCache</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Carbon</span>\<span class="hljs-title">CarbonImmutable</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Cache</span>;

<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AbstractDataCache</span>
</span>{
    <span class="hljs-comment">/**
     * Concrete class must implement and return the cache key
     */</span>
    <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCacheKey</span>(<span class="hljs-params"></span>): <span class="hljs-title">string</span></span>;

    <span class="hljs-comment">/**
     * Concrete class must know how to compute and return the data that need to be cached
     */</span>
    <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">computeCache</span>(<span class="hljs-params"></span>): <span class="hljs-title">mixed</span></span>;

    <span class="hljs-comment">/**
     * By default, data will be cached for an hour
     *
     * <span class="hljs-doctag">@return</span> CarbonImmutable
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCacheExpiration</span>(<span class="hljs-params"></span>): <span class="hljs-title">CarbonImmutable</span>
    </span>{
        <span class="hljs-keyword">return</span> CarbonImmutable::now()-&gt;addHour();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get</span>(<span class="hljs-params"></span>): <span class="hljs-title">mixed</span>
    </span>{
        <span class="hljs-keyword">return</span> Cache::remember(
            <span class="hljs-keyword">$this</span>-&gt;getCacheKey(),
            <span class="hljs-keyword">$this</span>-&gt;getCacheExpiration(),
            <span class="hljs-function"><span class="hljs-keyword">fn</span> (<span class="hljs-params"></span>) =&gt; $<span class="hljs-title">this</span>-&gt;<span class="hljs-title">computeCache</span>(<span class="hljs-params"></span>)
        )</span>;
    }

    <span class="hljs-comment">/**
     * Clear the cache data
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">clear</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        Cache::forget(<span class="hljs-keyword">$this</span>-&gt;getCacheKey());
    }

    <span class="hljs-comment">/**
     * Clear and re-cache the data
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rebuild</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;clear();
        <span class="hljs-keyword">$this</span>-&gt;get();
    }
}
</code></pre>
<p>Then I'll create <code>CountriesListDataCache</code> and extends and implements the required methods:</p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CountriesListDataCache</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">AbstractDataCache</span>
</span>{
    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCacheKey</span>(<span class="hljs-params"></span>): <span class="hljs-title">string</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">'countries-list'</span>;
    }

    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">computeCache</span>(<span class="hljs-params"></span>): <span class="hljs-title">array</span>
    </span>{
        <span class="hljs-keyword">return</span> Country::query()-&gt;pluck(<span class="hljs-string">'name'</span>, <span class="hljs-string">'id'</span>);
    }

    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCacheExpiration</span>(<span class="hljs-params"></span>): <span class="hljs-title">CarbonImmutable</span>
    </span>{
        <span class="hljs-keyword">return</span> CarbonImmutable::now()-&gt;addYear();
    }
}
</code></pre>
<p>Taking into action:</p>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">index</span>(<span class="hljs-params">CountriesListDataCache $dataCache</span>): <span class="hljs-title">JsonResponse</span>
</span>{
    $countries = $dataCache-&gt;get();

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JsonResponse(compact(<span class="hljs-string">'countries'</span>));
}

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">store</span>(<span class="hljs-params">
    CountryStoreRequest $request,
    CountriesListDataCache $dataCache
</span>): <span class="hljs-title">JsonResponse</span> </span>{
    $country = Country::create($request-&gt;validated());

    $dataCache-&gt;rebuild(); <span class="hljs-comment">// or clear</span>

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JsonResponse();
}
</code></pre>
<h3 id="heading-achievements">Achievements</h3>
<p>So with that, what do we achieve?</p>
<ul>
<li><p>A new layer to manage the caches (key, time, values)</p>
<ul>
<li><p>No more hardcoded string or string concat/manipulation</p>
</li>
<li><p>No more duct tape caching stuff</p>
</li>
</ul>
</li>
<li><p><strong>Maintaining</strong> the cache across the app is way <strong>better</strong>.</p>
<ul>
<li>We also have a dedicated folder for cache stuff.</li>
</ul>
</li>
<li><p>Can use the dependency injection just fine.</p>
</li>
<li><p><strong>Testable</strong> and super easy-to-write test cases.</p>
</li>
</ul>
<h3 id="heading-qa">Q/A</h3>
<ul>
<li><p>Q: What if I need some additional entities to compute the cache data?</p>
<ul>
<li>A: pass them to the <code>constructor</code> and then you can use them normally fine (same approaches as <strong>Queue</strong>)</li>
</ul>
</li>
<li><p>Q: How to use dependency injection?</p>
<ul>
<li><p>A: use the helper <code>app</code> function to get your desired instance for your computation.</p>
<ul>
<li>Additionally, I'd love to make the <code>computeCache</code> as same as the <code>handle</code> of Queue class, will do soon</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Well, that's that for the <strong>DataCache</strong> topic. I hope you guys enjoy it. I have been using that approach for some of my projects, so far so good.</p>
<p>If you have any other ideas or questions, comment below!</p>
<p>Cheers, and see you next time!</p>
]]></content:encoded></item><item><title><![CDATA[Remember this artisan command when you create a new table]]></title><description><![CDATA[Hey guys,
This command has been presented there for a while but I want to share it again 🥹
Before I usually do:

php artisan make:migration CreateAbcTables

php artisan make:model Abc

php artisan make:factory Abc

...


It's quite a hassle, isn't i...]]></description><link>https://sethphat.dev/remember-this-artisan-command-when-you-create-a-new-table</link><guid isPermaLink="true">https://sethphat.dev/remember-this-artisan-command-when-you-create-a-new-table</guid><category><![CDATA[Laravel]]></category><category><![CDATA[PHP]]></category><category><![CDATA[tips]]></category><category><![CDATA[tips and tricks]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Sun, 23 Jun 2024 06:12:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1719123138198/8124640a-4fd1-4601-89f7-14138f021e37.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>This command has been presented there for a while but I want to share it again 🥹</p>
<p>Before I usually do:</p>
<ul>
<li><p><code>php artisan make:migration CreateAbcTables</code></p>
</li>
<li><p><code>php artisan make:model Abc</code></p>
</li>
<li><p><code>php artisan make:factory Abc</code></p>
</li>
<li><p>...</p>
</li>
</ul>
<p>It's quite a hassle, isn't it? But actually, there is a way to do it in 1 go 😎</p>
<h2 id="heading-the-combination">The combination</h2>
<pre><code class="lang-bash">php artisan make:model Article --migration --factory
</code></pre>
<p>Once hitting this one, it will create for us:</p>
<ul>
<li><p>Article model</p>
</li>
<li><p>Migration (with <code>Schema::create('articles', ...)</code>), automatically do the "pluralize".</p>
</li>
<li><p>Article Factory (optional - if you want to write tests)</p>
</li>
</ul>
<h3 id="heading-additional-params">Additional params</h3>
<ul>
<li><p><code>--controller</code></p>
</li>
<li><p><code>--resource</code></p>
</li>
<li><p><code>--policy</code></p>
</li>
<li><p><code>--seed</code></p>
</li>
<li><p><code>--all</code>: this will create everything 😆</p>
</li>
</ul>
<p>IMO, I do think that model, migration &amp; factory are good enough for the first go. We can create a controller or policy or else in later 🤗.</p>
<h2 id="heading-additional-tips">Additional tips</h2>
<p>When creating a migration to change the schema of an existing table, try put the name like this:</p>
<ul>
<li><p>AddUserIdColumn<strong>To</strong>Users<strong>Table</strong></p>
</li>
<li><p>ChangeAmountType<strong>In</strong>Transactions<strong>Table</strong></p>
</li>
<li><p>DeleteUserId<strong>From</strong>Events<strong>Table</strong></p>
</li>
</ul>
<p>Laravel will extract the middle part, transform the table into the snack_case, and prepare the base code for us</p>
<pre><code class="lang-php">Schema::table(<span class="hljs-string">'{your_table}'</span>, ...);
</code></pre>
<p>Save a bit of your time, eh? 🥹</p>
<h2 id="heading-ending-words">Ending words</h2>
<p>Thank you for reading this, I believe many of us already know &amp; aware of these options. Save time and jump into productivity quicker.</p>
<p>Laravel is so beautiful 😍</p>
]]></content:encoded></item><item><title><![CDATA[Tools to draft your tech ideas]]></title><description><![CDATA[Hey guys,
Sometimes, somewhere, suddenly some ideas will arrive in our heads out of nowhere ⭐️. It is truly a bliss.
Don't waste those ideas and try to note or draft them ASAP. Later, we can review them deeply once again (in our free time) to see if ...]]></description><link>https://sethphat.dev/tools-to-draft-your-tech-ideas</link><guid isPermaLink="true">https://sethphat.dev/tools-to-draft-your-tech-ideas</guid><category><![CDATA[ideas]]></category><category><![CDATA[Startups]]></category><category><![CDATA[notes]]></category><category><![CDATA[tools]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Sun, 09 Jun 2024 02:01:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/_HtKNv8zs5s/upload/5012e3146652ece298121b0c31d317a1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>Sometimes, somewhere, suddenly some ideas will arrive in our heads out of nowhere ⭐️. It is truly a bliss.</p>
<p>Don't waste those ideas and try to note or draft them ASAP. Later, we can review them deeply once again (in our free time) to see if it's viable &amp; worth investing 😉.</p>
<p>Today I'll share with you guys the tools that I always use to draft my ideas. The factors that I choose for a great tool are:</p>
<ul>
<li><p>Easy to use</p>
</li>
<li><p>Simple design</p>
</li>
<li><p>Get started quickly</p>
</li>
</ul>
<h2 id="heading-note-amp-planning-notion">Note &amp; Planning: Notion</h2>
<p><a target="_blank" href="https://notion.so/">Notion</a> is awesome. Organizations around the world are using Notion every day.</p>
<p>Notion is really simple to use, we can create a quick page and note things as quickly as we can (on mobile). Once we get into our laptop/PC, we'll make the note become project info, planning, etc.</p>
<p>Worth mentioning: Notion is free (10 users) 🚀.</p>
<p>My usual template for a page:</p>
<pre><code class="lang-markdown"><span class="hljs-section"># (Idea name)</span>

<span class="hljs-section">## Information</span>

Add the info and what it does

<span class="hljs-section">## Monetization</span>

How to monetize the project?

<span class="hljs-section">## Competitors</span>

Add the competitors (if any)
</code></pre>
<h2 id="heading-diagram-excalidraw">Diagram: Excalidraw</h2>
<p><a target="_blank" href="https://excalidraw.com/">Excalidraw</a> is a free diagram maker in the "hand-drawn" style. It makes the diagram feel like we drew it using the pencil 🥹.</p>
<p>I've been using Excalidraw and using their paid plan (organize &amp; save the diagram on cloud, etc). It's really cool to add a bookmark "Draw diagram" and then draw things immediately 🤗.</p>
<h2 id="heading-design-ideas">Design ideas</h2>
<p>Well, this one is tough lol. However, we can take reference from the competitors, premium designs, etc.</p>
<p>Then we can draw a simple site using either: Excalidraw or Figma.</p>
<p>Additionally, you can take the reference from <a target="_blank" href="https://tailwindui.com/">TailwindUI</a> or <a target="_blank" href="https://flowbite.com/">Flowbite</a> (yeah I'm a fan of TailwindCSS, it's awesome).</p>
<p>Just make sure that your product will have a unique &amp; awesome look 😎.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Well, that's all guys.</p>
<p>There is one suggestion left: before going to the planning &amp; implementation, make sure to research the competitors (pricing, features, customers, etc). That's quite important to ensure that you don't waste your time or energy (or if you want to do it for learning, or coding purposes, well feel free to do so 🥰).</p>
<p>Thank you for reading and see you in the next post soon!</p>
]]></content:encoded></item><item><title><![CDATA[Using Gmail as the Email Client for your domain (Free)]]></title><description><![CDATA[Hey guys,
Today I'll share with you how to set up Gmail as the Email Client for your domain, and of course, it is totally FREE 😎.
We all know that when releasing a new product, especially if you're on the "solo" path, minimal cost would definitely h...]]></description><link>https://sethphat.dev/using-gmail-as-the-email-client-for-your-domain-free</link><guid isPermaLink="true">https://sethphat.dev/using-gmail-as-the-email-client-for-your-domain-free</guid><category><![CDATA[email]]></category><category><![CDATA[solopreneur ]]></category><category><![CDATA[Startups]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Fri, 24 May 2024 04:56:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1716526535621/a117c200-0744-42fd-a23a-1cb93ca990a0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>Today I'll share with you how to set up Gmail as the Email Client for your domain, and of course, it is totally FREE 😎.</p>
<p>We all know that when releasing a new product, especially if you're on the "solo" path, minimal cost would definitely help us do things in the long run 👀.</p>
<p>Email is one of the important things that we need to take care of. That's where customers will reach out to us, where we reply to them, and so on.</p>
<p>Email hosting costs around <strong><em>$1~$5</em></strong> (e.g.: Google would cost $3.6 per month for the first year, and $7.2 for upcoming years).</p>
<p>Gmail is simply beautiful and has great UI/UX, mobile app, etc.</p>
<h2 id="heading-introduce-to-improvmx">Introduce to ImprovMX</h2>
<p>Home page: <a target="_blank" href="https://improvmx.com/">https://improvmx.com/</a></p>
<p>ImprovMX is a service that will forward emails to a configured email account. Basically, this is how it works:</p>
<p>Emails =&gt; ImprovMX =&gt; send to your Gmail account.</p>
<p>ImprovMV does offer a FREE version with fair usage. Even though your emails will be sent in the lower priority you don't have to worry, the delay would be around <strong>seconds.</strong></p>
<p>Once you added your domain there, you can set rules like this:</p>
<ul>
<li><p>support@your-domain.com =&gt; your-gmail@gmail.com</p>
</li>
<li><p>business@your-domain.com =&gt; your-other-gmail@gmail.com</p>
</li>
<li><p>*@your-domain.com =&gt; your-fallback-gmail@gmail.com</p>
</li>
</ul>
<p>Isn't it cool? 😎</p>
<h2 id="heading-how-to-send-email-using-gmail-client-then">How to send email using Gmail Client then?</h2>
<p>Yup, here is the original guide from ImprovMX <a target="_blank" href="https://improvmx.com/guides/send-emails-using-gmail/">https://improvmx.com/guides/send-emails-using-gmail/</a></p>
<p>Basically, add a new sender email, do some verifications and you're all set 🚀.</p>
<h2 id="heading-pros-cons-and-considerations">PROs, CONs, and considerations</h2>
<h3 id="heading-pros">PROs</h3>
<ul>
<li><p>FREE</p>
</li>
<li><p>Easy setup</p>
</li>
<li><p>Suitable for starter products and small teams (~10 people)</p>
</li>
</ul>
<h3 id="heading-cons">CONs</h3>
<ul>
<li>You can't change the profile image of the sender's email.</li>
</ul>
<h3 id="heading-considerations">Considerations</h3>
<ul>
<li><p>Remember to add SPF, DKIM, and DMARC for better email deliverance (avoid "Spam")</p>
</li>
<li><p>When your product is growing so well, consider switching to Google Worksuite. Imagine headcounts increase, you will have a big pain managing emails 🥹</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Well that's it guys, that's a trick to help you guys in your early-stage solo products.</p>
<p>Happy friyay and thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[A simple useLoading composable for your Vue 3 projects]]></title><description><![CDATA[Hey guys,
Wanna share with you all a simple Vue composable function that lies in every Vue project of mine 😎.
Please meet the useLoading 🚀 A simple reusable composable that handles loading state flawlessly for you ❤️.
Before "useLoading", before co...]]></description><link>https://sethphat.dev/a-simple-useloading-composable-for-your-vue-3-projects</link><guid isPermaLink="true">https://sethphat.dev/a-simple-useloading-composable-for-your-vue-3-projects</guid><category><![CDATA[Frontend Development]]></category><category><![CDATA[Vue.js]]></category><category><![CDATA[vue]]></category><category><![CDATA[frontend]]></category><category><![CDATA[PHP]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Mon, 13 May 2024 06:52:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715582975111/db37b70e-4a79-4a57-bf6f-bc5c6bafb869.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>Wanna share with you all a simple Vue composable function that lies in every Vue project of mine 😎.</p>
<p>Please meet the <code>useLoading</code> 🚀 A simple reusable composable that handles loading state flawlessly for you ❤️.</p>
<h2 id="heading-before-useloading-before-composable">Before "useLoading", before composable</h2>
<p>I (and most of us) have been using this kind of approach before composable 🤣</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> isLoading = ref(<span class="hljs-literal">false</span>);

<span class="hljs-keyword">const</span> loadUsers = <span class="hljs-keyword">async</span> () =&gt; {
    isLoading.value = <span class="hljs-literal">true</span>;

    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> getUsers().catch(<span class="hljs-function">() =&gt;</span> <span class="hljs-literal">undefined</span>)); <span class="hljs-comment">// catch to avoid throw</span>

    isLoading.value = <span class="hljs-literal">false</span>;

    users.value = res?.users ?? [];
};

onMounted(loadUsers);
</code></pre>
<p>So before when composable wasn't a thing, we likely had to copy/paste to every component that needed the <code>isLoading</code> indicator.</p>
<h2 id="heading-vues-useloading-composable">Vue's useLoading composable</h2>
<p>With the <a target="_blank" href="https://vuejs.org/guide/reusability/composables">composable</a> introduced in Vue 3. It allows us to create multiple reusable pieces.</p>
<p>So I created this for my projects this bad boy. <code>useLoading</code> composable is a global one and can be reused everywhere.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/composables/useLoading.ts</span>

<span class="hljs-keyword">import</span> { ref } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useLoading = <span class="hljs-function">(<span class="hljs-params">initializedState = <span class="hljs-literal">false</span></span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> isLoading = ref(initializedState);

  <span class="hljs-keyword">const</span> startLoading = <span class="hljs-function">() =&gt;</span> {
    isLoading.value = <span class="hljs-literal">true</span>;
  };

  <span class="hljs-keyword">const</span> stopLoading = <span class="hljs-function">() =&gt;</span> {
    isLoading.value = <span class="hljs-literal">false</span>;
  };

  <span class="hljs-keyword">const</span> withLoading = (handler: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">void</span>&gt;) =&gt; <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">if</span> (isLoading.value) {
      <span class="hljs-keyword">return</span>;
    }

    startLoading();

    <span class="hljs-keyword">await</span> handler().catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> <span class="hljs-built_in">console</span>.error(err));

    stopLoading();
  };

  <span class="hljs-keyword">return</span> {
    isLoading,
    startLoading,
    stopLoading,
    withLoading,
  };
};
</code></pre>
<h3 id="heading-default-usage">Default usage</h3>
<p>By default, you can manually trigger <code>startLoading</code> and <code>stopLoading</code>, then use <code>isLoading</code> to check the state.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> { isLoading, startLoading, stopLoading } = useLoading();

startLoading();
<span class="hljs-comment">// do something so long here</span>
stopLoading();
</code></pre>
<h3 id="heading-hof-withloading">HoF: withLoading</h3>
<p>The awesomeness comes from this bad boy 😎. Having a high-order function to wrap our logic/handlers there and it's automatically triggered <code>startLoading</code> on start and <code>stopLoading</code> on finished.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> { isLoading, withLoading } = useLoading();

<span class="hljs-keyword">const</span> loadUsers = withLoading(<span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> getUsers().catch(<span class="hljs-function">() =&gt;</span> <span class="hljs-literal">undefined</span>));
    users.value = res.users ?? [];
});

onMounted(loadUsers);
</code></pre>
<p>Not to mention, the <code>withLoading</code> will not trigger another call if <code>isLoading === true</code> , thus ensuring we don't dispatch a lot of API calls/handlers/etc by mistake 🛡️.</p>
<h2 id="heading-moving-forward">Moving forward</h2>
<p>Not only <code>useLoading</code>, but also there are several cases which you could create as a composable and reuse:</p>
<ul>
<li><p><code>useError</code> (error from API, error from FE, etc)</p>
</li>
<li><p><code>useValidation</code> (simple frontend validation)</p>
</li>
<li><p><code>useRouteParams</code> (return the Vue's router params in strict types)</p>
</li>
<li><p><code>useRouteSearchParams</code> (same as above but for URL Search Params aka GET params)</p>
</li>
<li><p>and many more...</p>
</li>
</ul>
<h2 id="heading-closing-topic">Closing topic</h2>
<p>And that's that, I hope you will like the <code>useLoading</code> composable. I really like the HoF <code>withLoading</code> above.</p>
<p>Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Laravel: Go for Passwordless Authentication]]></title><description><![CDATA[Hey guys,
Authentication topic today 😎
We all know that Laravel ships the default Email & Password authentication for us. The good'ol traditional way.
However, it's 2024 now. By registering using Email & Password, then users have to verify their ema...]]></description><link>https://sethphat.dev/laravel-go-for-passwordless-authentication</link><guid isPermaLink="true">https://sethphat.dev/laravel-go-for-passwordless-authentication</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[laravelframework]]></category><category><![CDATA[authentication]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Fri, 10 May 2024 07:43:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1715326088043/cdf58932-1b7c-4fa2-8d64-9fa4297d3b55.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>Authentication topic today 😎</p>
<p>We all know that Laravel ships the default Email &amp; Password authentication for us. The good'ol traditional way.</p>
<p>However, it's 2024 now. By registering using Email &amp; Password, then users have to verify their emails. It's way bad UX and reduces your sign-up rates.</p>
<p>The <code>passwordless</code> is here to save the day 😎</p>
<h2 id="heading-the-pros-of-using-passwordless-authentication">The PROs of using Passwordless Authentication</h2>
<ul>
<li><p>Sign-up user flow will be really fast, with fewer inputs (or even 0 inputs) 😎</p>
</li>
<li><p>The pain of remembering passwords is gone 🥰</p>
</li>
<li><p>Save storage cuz we don't really store users' passwords (encrypted obviously) in our DB 🥹</p>
</li>
<li><p>Verify the user's email in 1 go, 2 birds with 1 stone.</p>
</li>
</ul>
<h2 id="heading-laravel-passwordless-authentication-the-approaches">Laravel Passwordless Authentication - The Approaches</h2>
<h3 id="heading-social-auth">Social Auth</h3>
<p>By utilizing <a target="_blank" href="https://laravel.com/docs/11.x/socialite">Laravel Socialite</a>, we can provide a quick sign-up/sign-in flow using:</p>
<ul>
<li><p>Google</p>
</li>
<li><p>GitHub</p>
</li>
<li><p>(or any awesome platform out there)</p>
</li>
</ul>
<p>After users sign in, Laravel Socialite will give you some basic details: open id, full name, email, and profile picture.</p>
<p>Then simply do an <code>upsert</code> operation and <code>Auth::login($user)</code> to sign your user in 😎</p>
<h3 id="heading-magic-link-via-email">Magic Link via Email</h3>
<p>For this, we only need users to input their email and their names, and then we are all good 🌹</p>
<p>For this, you might need to implement your own <code>generated_magic_links</code> table, e.g:</p>
<ul>
<li><p>id</p>
</li>
<li><p>user_id</p>
</li>
<li><p>hash</p>
</li>
</ul>
<p>You can create a temporary <code>user</code> with <code>email_verified_at = NULL</code> . Then send out the email using <a target="_blank" href="https://laravel.com/docs/11.x/mail#main-content">Laravel Mail</a> feature.</p>
<p>On the user's first visit, you <code>touch</code> the <code>email_verified_at</code> , then log them in 🔥</p>
<p>Easy right?</p>
<h3 id="heading-other-ways">Other ways</h3>
<ul>
<li><p>Send SMS (cost more lol)</p>
</li>
<li><p>Use TOTP (time-based one-time password as temporary password)</p>
</li>
</ul>
<h2 id="heading-considerations">Considerations</h2>
<ul>
<li>To increase the security, we can apply the <code>two-factor authentication</code> method after logging in.</li>
</ul>
<h2 id="heading-conclusions">Conclusions</h2>
<p>My applications use Passwordless authentication, check out <a target="_blank" href="https://renderpdf.io">RenderPDF.io</a> as an example 😎.</p>
<p>Using passwordless is really cool and hassle-free. Fewer inputs, a happier life.</p>
<p>Happy coding guys!</p>
]]></content:encoded></item><item><title><![CDATA[Laravel: Generate a unique request ID for each request and put it into logs]]></title><description><![CDATA[Hey guys,
Today I'll share with you a tip that will skyrocket your production debugging workflow 😎
We're going to generate a unique ID for each request, which we can use the request ID to search for every related log of the given request ID.
Thus, i...]]></description><link>https://sethphat.dev/laravel-generate-a-unique-request-id-for-each-request-and-put-it-into-logs</link><guid isPermaLink="true">https://sethphat.dev/laravel-generate-a-unique-request-id-for-each-request-and-put-it-into-logs</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[tips]]></category><category><![CDATA[tricks]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Mon, 29 Apr 2024 03:10:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1714360113255/81dbf522-5acf-4f44-a4bd-16cf4afac41d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>Today I'll share with you a tip that will <strong>skyrocket your production debugging workflow</strong> 😎</p>
<p>We're going to generate a unique ID for each request, which we can use the request ID to search for every related log of the given request ID.</p>
<p>Thus, if any issue happens, we can try to find the request ID (or customers give us), and then jump immediately to the logs and do the surgery 👀.</p>
<p>Pretty handy, isn't it? 💪</p>
<p>Let's jump to the topic &amp; the implementation.</p>
<h2 id="heading-requirement">Requirement</h2>
<p>You'll need <strong>Laravel 11</strong> because we are going to use the <code>Context</code> feature.</p>
<p>If you haven't learned about <code>Context</code>, visit this page of mine:</p>
<ul>
<li><a target="_blank" href="https://sethphat.dev/use-laravel-context-in-a-lovely-maintainable-way">Laravel Context &amp; how to use it in a maintainable way</a></li>
</ul>
<h3 id="heading-why-context">Why Context?</h3>
<blockquote>
<p>Laravel's "context" capabilities enable you to capture, retrieve, and share information throughout requests, jobs, and commands executing within your application. This captured information is also included in logs written by your application, giving you deeper insight into the surrounding code execution history that occurred before a log entry was written and allowing you to trace execution flows throughout a distributed system.</p>
</blockquote>
<p>Read more at: <a target="_blank" href="https://laravel.com/docs/11.x/context#introduction"><strong>Laravel Context</strong></a></p>
<p>In short terms:</p>
<ul>
<li><p>So if a context presents in the request's lifecycle, Laravel will automatically log it every time we write a log using <code>Log</code> facade</p>
</li>
<li><p>Context is only available for each request, and queue job (Octane is covered too). There won't be any conflict or race condition.</p>
</li>
</ul>
<pre><code class="lang-php">Context::add(<span class="hljs-string">'hello'</span>, <span class="hljs-string">'world'</span>);
Log::info(<span class="hljs-string">'Hello'</span>); <span class="hljs-comment">// you will see the [datetime] Hello { "contexts": { "hello": "world" } } in the log record</span>
</code></pre>
<h2 id="heading-implementation">Implementation</h2>
<h3 id="heading-create-a-middleware-to-generateaccept-id">Create a middleware to generate/accept ID</h3>
<p>We'll have 2 scenarios:</p>
<ul>
<li><p>If there is a <code>X-REQUEST-ID</code> header with a valid ID, we'll continue to use that ID</p>
<ul>
<li><p>This will help trace <strong>ALL REQUESTS</strong> of a particular user's lifecycle.</p>
</li>
<li><p>I'll use <code>X-REQUEST-ID</code>, you can use any key that you like 🥹.</p>
</li>
</ul>
</li>
<li><p>If not, we'll generate a new unique ID</p>
</li>
</ul>
<p>Let's hit the command to create a new middleware:</p>
<blockquote>
<p>php artisan make:middleware UseRequestId</p>
</blockquote>
<p>And the implementation:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Middleware</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Str</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UseRequestId</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span>(<span class="hljs-params">Request $request, <span class="hljs-built_in">Closure</span> $next</span>): <span class="hljs-title">Response</span>
    </span>{
        <span class="hljs-comment">// get existing id or generate new one</span>
        $requestId = $request-&gt;header(<span class="hljs-string">'X-REQUEST-ID'</span>)
            ?? (<span class="hljs-keyword">string</span>) Str::ulid();

        <span class="hljs-comment">// add to context</span>
        Context::add(<span class="hljs-string">'requestId'</span>, $requestId);

        <span class="hljs-keyword">return</span> $next($request);
    }
}
</code></pre>
<h3 id="heading-register-the-middleware">Register the middleware</h3>
<p>Since my application is <strong>an API service</strong>, I'll apply the middleware to the <code>api</code> group.</p>
<p>Open <code>bootstrap/app.php</code> and register it:</p>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Middleware</span>\<span class="hljs-title">UseRequestId</span>;


-&gt;withMiddleware(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Middleware $middleware</span>) </span>{
    $middleware-&gt;prependToGroup(UseRequestId::class);
})
</code></pre>
<p>Using <code>prependToGroup</code> to ensure this middleware will be resolved at the top (global =&gt; this middleware =&gt; other middleware of <code>api</code> group)</p>
<h3 id="heading-add-a-route-and-try-it-out">Add a route and try it out 🥰</h3>
<p>Add a sample API</p>
<pre><code class="lang-php"><span class="hljs-comment">// api.php</span>

Route::get(<span class="hljs-string">'/ping'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    Log::info(<span class="hljs-string">'pong'</span>);

    <span class="hljs-keyword">return</span> response()-&gt;json([]);
});
</code></pre>
<p>Open the log file to check (<code>laravel.log</code> for the basic driver or <code>laravel-{date}.log</code> for <code>daily</code> driver).</p>
<p>We should see something like:</p>
<pre><code class="lang-php">[<span class="hljs-number">2024</span><span class="hljs-number">-04</span><span class="hljs-number">-28</span> <span class="hljs-number">13</span>:<span class="hljs-number">22</span>:<span class="hljs-number">59</span>] local.INFO: Pong  {<span class="hljs-string">"requestId"</span>:<span class="hljs-string">"01HWJDZ12P3CE4JDY3912YSDEA"</span>}
</code></pre>
<p>To ensure it will print the same request ID, you can try to log multiple times/multiple files in the same lifecycle. For example:</p>
<pre><code class="lang-php">Log::info(<span class="hljs-string">'ping'</span>);
<span class="hljs-comment">// do something</span>
Log::info(<span class="hljs-string">'pong'</span>);
<span class="hljs-comment">// do something more</span>
Log::info(<span class="hljs-string">'end'</span>);
</code></pre>
<p>Result:</p>
<pre><code class="lang-php">[<span class="hljs-number">2024</span><span class="hljs-number">-04</span><span class="hljs-number">-28</span> <span class="hljs-number">13</span>:<span class="hljs-number">26</span>:<span class="hljs-number">35</span>] local.INFO: ping  {<span class="hljs-string">"requestId"</span>:<span class="hljs-string">"01HWJE5MD8DQ24DBDN5R8TXK7K"</span>}
[<span class="hljs-number">2024</span><span class="hljs-number">-04</span><span class="hljs-number">-28</span> <span class="hljs-number">13</span>:<span class="hljs-number">26</span>:<span class="hljs-number">35</span>] local.INFO: pong  {<span class="hljs-string">"requestId"</span>:<span class="hljs-string">"01HWJE5MD8DQ24DBDN5R8TXK7K"</span>}
[<span class="hljs-number">2024</span><span class="hljs-number">-04</span><span class="hljs-number">-28</span> <span class="hljs-number">13</span>:<span class="hljs-number">26</span>:<span class="hljs-number">35</span>] local.INFO: end  {<span class="hljs-string">"requestId"</span>:<span class="hljs-string">"01HWJE5MD8DQ24DBDN5R8TXK7K"</span>}
</code></pre>
<blockquote>
<p>Context already covered &amp; battle-tested from Laravel too 👀</p>
</blockquote>
<h3 id="heading-add-the-id-to-the-response">Add the ID to the response</h3>
<p>Last part, we need to send the ID to the API consumers, so they can send the same ID or for error reporting purposes.</p>
<p>We'll transform a bit of the middleware <code>handle</code> method:</p>
<pre><code class="lang-php"><span class="hljs-comment">/** <span class="hljs-doctag">@var</span> \Illuminate\Http\Response $response */</span>
$response = $next($request);

<span class="hljs-keyword">return</span> $response-&gt;header(<span class="hljs-string">'X-REQUEST-ID'</span>, $requestId);
</code></pre>
<p>When hitting the <code>$next</code>, Laravel will process everything and give us the <strong>FINAL RESPONSE (after Error/Exception too)</strong>.</p>
<p>So we can add the ID into the header (or body if you want) 😉</p>
<h2 id="heading-result">Result</h2>
<p>Now our requests are bound with request IDs. With means:</p>
<ul>
<li><p>Easier for us to search for logs of a given ID 😎 Debugging is much faster &amp; better now.</p>
<ul>
<li>If you use any logging platform, surely it can search wayyy faster than normal text search ❤️.</li>
</ul>
</li>
<li><p>Customers/API consumers can know their "request ID", if it has any issue, they can extract the request ID and report the problem to us 👀</p>
</li>
<li><p>Additionally, you can record more data in the Context, e.g.:</p>
<ul>
<li><p>Logged In User ID</p>
</li>
<li><p>URL</p>
</li>
<li><p>Timezone</p>
</li>
<li><p>etc</p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Well, happy coding &amp; happy debugging guys.</p>
<p>Until the next blog post 😎. Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Render HTML to PDF with RenderPDF.io]]></title><description><![CDATA[Hey guys,
I'm super excited to share with you all about my latest work - RenderPDF.io 😎
We all know that rendering HTML to PDF is not so fun and game.
You have to research a bunch of things, then try out a lot of CSS styling, and even have to instal...]]></description><link>https://sethphat.dev/render-html-to-pdf-with-renderpdfio</link><guid isPermaLink="true">https://sethphat.dev/render-html-to-pdf-with-renderpdfio</guid><category><![CDATA[services]]></category><category><![CDATA[APIs]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[PHP]]></category><category><![CDATA[pdf]]></category><category><![CDATA[html to pdf]]></category><category><![CDATA[htmltopdfconvert]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Sun, 21 Apr 2024 14:08:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1713707959072/808a18dc-6629-44eb-9581-9533d6b8a4f5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>I'm super excited to share with you all about my latest work - <a target="_blank" href="https://renderpdf.io">RenderPDF.io</a> 😎</p>
<p>We all know that rendering HTML to PDF is not so fun and game.</p>
<p>You have to research a bunch of things, then try out a lot of CSS styling, and even have to install more libraries or deploy new stuff and manage them.</p>
<p>It's definitely time-consuming &amp; hard to maintain, isn't it? But not anymore 🥰.</p>
<h2 id="heading-what-is-renderpdfio">What is RenderPDF.io?</h2>
<p><a target="_blank" href="http://RenderPDF.io">RenderPDF.io</a> provides <strong>developer-friendly APIs</strong> to help people render their PDFs in seconds 🚀.</p>
<p>Just send us the HTML content and we'll respond with the rendered PDF file 🚀. Get rid of the hard &amp; tedious work and focus on your features/delivery ⭐️.</p>
<p>You can find us at:</p>
<ul>
<li><p>Home page: <a target="_blank" href="https://renderpdf.io/">https://renderpdf.io/</a></p>
</li>
<li><p>Documentation: <a target="_blank" href="https://docs.renderpdf.io/">https://docs.renderpdf.io/</a></p>
</li>
<li><p>Get FREE API Key: <a target="_blank" href="https://renderpdf.io/login">https://renderpdf.io/login</a> (within a few clicks, I promised 😎)</p>
</li>
</ul>
<p>Powered by the <strong>Chromium</strong> engine, enabling us to use any latest CSS styling and give you modern PDF files 🚀.</p>
<p>We have some examples on the home page, check them out 🔥.</p>
<h3 id="heading-free-usage">Free Usage</h3>
<p>Yes, Free users get <strong>500 PDFs</strong> per month - the highest free usage compared to others out there 😎.</p>
<h3 id="heading-examples">Examples</h3>
<h4 id="heading-integrate-with-renderpdfio-using-typescript">Integrate with RenderPDF.io using TypeScript</h4>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-keyword">const</span> httpClient = axios.create({
  baseUrl: <span class="hljs-string">'https://renderpdf.io/api'</span>
  headers: {
    Authorization: <span class="hljs-string">'Bearer YOUR_API_KEY'</span>,
  },
});

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">renderPdf</span>(<span class="hljs-params">htmlContent: <span class="hljs-built_in">string</span></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">string</span>&gt; </span>{
  <span class="hljs-keyword">return</span> httpClient.post(<span class="hljs-string">'/pdfs/render-sync'</span>, {
    htmlContent,
    <span class="hljs-comment">// add more options here if you need</span>
  }).then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> res.data.fileUrl));
}

<span class="hljs-comment">// a "Hello World" PDF ❤️</span>
<span class="hljs-keyword">const</span> pdfUrl = <span class="hljs-keyword">await</span> renderPdf(<span class="hljs-string">'Hello World'</span>);
</code></pre>
<h4 id="heading-integrate-with-renderpdfio-using-php-amp-laravel-using-http-client-facade">Integrate with RenderPDF.io using PHP &amp; Laravel, using HTTP Client Facade</h4>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Http</span>;

$response = Http::withToken(<span class="hljs-string">'YOUR_API_TOKEN'</span>)
    -&gt;asJson()
    -&gt;post(<span class="hljs-string">'https://renderpdf.io/api/pdfs/render-sync'</span>, [
        <span class="hljs-string">'htmlContent'</span> =&gt; <span class="hljs-string">'Render me a PDF now!!!'</span>,
        <span class="hljs-comment">// add more options here if you need</span>
    ]);

<span class="hljs-comment">// Your PDF is ready sir :muscle:</span>
$fileUrl = $response-&gt;json(<span class="hljs-string">'fileUrl'</span>);
</code></pre>
<h2 id="heading-upcoming-features-of-renderpdfio">Upcoming Features of RenderPDF.io</h2>
<p>Indeed, it is still under elevating process and there will be more and more features upcoming soon to make your life easier 🥰:</p>
<ul>
<li><p>Free PDF templates 👀</p>
</li>
<li><p>Document Template management 😎</p>
</li>
<li><p>Template Editor 🔥</p>
</li>
<li><p>and still thinking more hehe</p>
</li>
</ul>
<p>Stays tuned.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Well, that's it. I hope you will like <a target="_blank" href="https://renderpdf.io">RenderPDF.io</a> and give it a try, it is totally worth the try ❤️.</p>
<p>Happy weekend and see you all on the next tech talk 😉!</p>
]]></content:encoded></item><item><title><![CDATA[Use Laravel Context in a lovely & maintainable way 🥰]]></title><description><![CDATA[Hey guys,
For everybody that has been upgraded to Laravel 11, cheers (me too). Laravel 11 has shipped a lot of bug fixes, improvements, and some new awesome features.
Today we'll talk about the Context - one of the new features of Laravel 11.
Laravel...]]></description><link>https://sethphat.dev/use-laravel-context-in-a-lovely-maintainable-way</link><guid isPermaLink="true">https://sethphat.dev/use-laravel-context-in-a-lovely-maintainable-way</guid><category><![CDATA[Laravel]]></category><category><![CDATA[PHP]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[software architecture]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Fri, 12 Apr 2024 04:47:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1712897200434/4460881c-d727-46ff-81e6-497410b99c11.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>For everybody that has been upgraded to Laravel 11, cheers (me too). Laravel 11 has shipped a lot of bug fixes, improvements, and some new awesome features.</p>
<p>Today we'll talk about the <strong>Context</strong> - one of the new features of Laravel 11.</p>
<h2 id="heading-laravel-context">Laravel Context</h2>
<p><a target="_blank" href="https://laravel.com/docs/11.x/context#main-content"><strong>Laravel Context</strong></a> is the easiest way to manage &amp; share data in a request (or jobs, commands).</p>
<p>It's a layer to handle the <strong>shortage cache</strong> of your request.</p>
<p>Using PHP-FPM, we all know that every request is independent, thus allowing us to create static properties to store in-memory data and after the request ends, data will be flushed.</p>
<p>However, commands &amp; jobs are different. They are long-lived processes, the data won't be flushed until the process ends. This makes thing harder to manage and share data.</p>
<ul>
<li>Same for Laravel Octane, our applications will be run in a long-lived process.</li>
</ul>
<p>Won't be the case anymore, Laravel Context has our backs now 😎. It will ensure Context is only available during a request lifecycle (HTTP, Job, Command, and Octane's request).</p>
<h3 id="heading-using-context">Using Context</h3>
<p>As same as how you using <code>Cache</code> facade:</p>
<pre><code class="lang-php">Context::set(<span class="hljs-string">'seth'</span>, $seth);
Context::get(<span class="hljs-string">'seth'</span>);
Context::forget(<span class="hljs-string">'seth'</span>);
<span class="hljs-comment">// and so many others</span>
</code></pre>
<p>Unlike <code>Cache</code>, you can store anything in-memory: string, int, Eloquent Model, Objects, etc which is really cool 😎.</p>
<p>All of them will be cleaned after the request ends.</p>
<h2 id="heading-use-context-in-a-lovely-way">Use Context in a lovely way</h2>
<p>As we can see, whenever we need to use <code>Context</code>, we have to specify the <code>$key</code></p>
<p>If we use hardcoded string, later it will become a big mess, and hard to track usage.</p>
<p>Let's create a simple class to access your context.</p>
<p>For example, I'll have this</p>
<pre><code class="lang-php"><span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Contexts</span>; <span class="hljs-comment">// app/Contexts folder</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">ApiKey</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Context</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CurrentApiKeyContext</span>
</span>{
    <span class="hljs-keyword">protected</span> <span class="hljs-built_in">static</span> <span class="hljs-keyword">string</span> $key = <span class="hljs-string">'currentApiKey'</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">set</span>(<span class="hljs-params">ApiKey $apiKey</span>): <span class="hljs-title">void</span>
    </span>{
        Context::add(<span class="hljs-built_in">static</span>::$key, $apiKey);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get</span>(<span class="hljs-params"></span>): <span class="hljs-title">ApiKey</span>
    </span>{
        <span class="hljs-keyword">return</span> Context::get(<span class="hljs-built_in">static</span>::$key);
    }
}
</code></pre>
<p>And when the authentication happens, I'd do:</p>
<pre><code class="lang-php"><span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Middleware</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Contexts</span>\<span class="hljs-title">CurrentApiKeyContext</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthenticateApiKey</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span>(<span class="hljs-params">Request $request, <span class="hljs-built_in">Closure</span> $next</span>): <span class="hljs-title">Response</span>
    </span>{
        $apiKey = ApiKey::findByKey($request-&gt;bearerToken());
        <span class="hljs-keyword">if</span> (!$apiKey) {
            <span class="hljs-keyword">return</span> response()-&gt;json([], <span class="hljs-number">401</span>);
        }

        <span class="hljs-comment">// set key</span>
        CurrentApiKeyContext::set($apiKey);

        <span class="hljs-keyword">return</span> $next($apiKey);
    }
}
</code></pre>
<p>After that, whenever I need to access the <code>currentApiKey</code>, I'd do:</p>
<pre><code class="lang-php"><span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Contexts</span>\<span class="hljs-title">CurrentApiKeyContext</span>;

CurrentApiKeyContext::get(); <span class="hljs-comment">// ApiKey instance</span>
</code></pre>
<h3 id="heading-the-pros">The PROs</h3>
<ul>
<li><p>Your <code>$key</code> will be defined once and maintained inside a class</p>
</li>
<li><p>Your data will be strictly-typed &amp; IDE-friendly</p>
<ul>
<li>No more <code>/** @var ... */</code> pain, I feel that</li>
</ul>
</li>
<li><p>Easy to write unit test cases</p>
</li>
</ul>
<h3 id="heading-the-cons">The CONs</h3>
<ul>
<li>You have a new layer to manage</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Everything is a trade-off in tech, and since the PROs are better, why don't we go that? 😎</p>
<p>Let's just not make it work, let's make it better &amp; easier to manage in a later stage ❤️.</p>
<p>Cheers, and happy Friday!</p>
]]></content:encoded></item><item><title><![CDATA[How about using PHP Enums for Results?]]></title><description><![CDATA[Hey guys,
Weekend post again hehe 😎. I'd like to share my latest approach and I actually find it really cool 🥹.
I've been using PHP Enums not only for options, types, etc but also for Result responses now ⭐️
Lemme show you how it works and how awes...]]></description><link>https://sethphat.dev/how-about-using-php-enums-for-results</link><guid isPermaLink="true">https://sethphat.dev/how-about-using-php-enums-for-results</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[php8]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Sun, 07 Apr 2024 02:27:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1712456032559/dbe43b7d-7603-4996-bb13-6f7601df7b72.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>Weekend post again hehe 😎. I'd like to share my latest approach and I actually find it really cool 🥹.</p>
<p>I've been using PHP Enums not only for options, types, etc but also for Result responses now ⭐️</p>
<p>Lemme show you how it works and how awesome is that 🚀.</p>
<h2 id="heading-results-over-exceptions">Results over Exceptions</h2>
<p>This is one of the practices I've been using and I love it, I also have a post for that: <a target="_blank" href="https://sethphat.dev/no-throw-and-happier-life"><strong>No throws and happier life</strong></a></p>
<p><em>TL;DR</em>:</p>
<ul>
<li><p>Throw is slow</p>
</li>
<li><p>Untyped</p>
<ul>
<li>E.g. if your function throws 4 exceptions =&gt; 4 untyped paths.</li>
</ul>
</li>
<li><p>Increase production errors</p>
<ul>
<li>Callers won't usually wrap the <code>try/catch</code></li>
</ul>
</li>
<li><p>It reduces the reusable purpose of your function</p>
</li>
</ul>
<p>I really like how this package <a target="_blank" href="https://github.com/supermacro/neverthrow?tab=readme-ov-file#a-note-on-the-package-name"><code>neverthrow</code></a> add their reasons as well:</p>
<blockquote>
<p><code>Throw</code>ing and <code>catching</code> is very similar to using <code>goto</code> statements - in other words; it makes reasoning about your programs harder. Secondly, by using <code>throw</code> you make the assumption that the caller of your function is implementing <code>catch</code>. This is a known source of errors. Example: One dev <code>throw</code>s and another dev uses the function without prior knowledge that the function will throw. Thus, and edge case has been left unhandled and now you have unhappy users, bosses, cats, etc.</p>
<p>With all that said, there are definitely good use cases for throwing in your program. But much less than you might think.</p>
</blockquote>
<h2 id="heading-use-php-enum-as-the-result">Use PHP Enum as the Result</h2>
<p>Yeah, here come the main dishes 😎</p>
<p>Everything looks easier with a real-life example, so let's create a <code>Result</code> class for creating a Vacation Leave Request.</p>
<h3 id="heading-the-result-class">The Result class</h3>
<p>I will create a <code>CreateLeaveRequestResult</code></p>
<pre><code class="lang-php">enum CreateLeaveRequestResult
{
    <span class="hljs-comment">/**
     * When the creating leave request is overlap with 
     * another leave requests
     */</span>
    <span class="hljs-keyword">case</span> HAS_CONFLICTED_DATES;

    <span class="hljs-comment">/**
     * When the user doesn't have enough leave balance
     */</span>
    <span class="hljs-keyword">case</span> INSUFFICIENT_BALANCE;

    <span class="hljs-comment">/**
     * All good and can create a new leave request
     */</span>
    <span class="hljs-keyword">case</span> SUCCESS;
}
</code></pre>
<p>As we can see, with some simple notes &amp; cases, we all know the outcome of the function that we are going to trigger. It makes our life easier ✅.</p>
<h3 id="heading-handle-logic-and-return-the-result">Handle logic and return the Result</h3>
<p>I have a <code>LeaveRequestService@create</code> method like this:</p>
<pre><code class="lang-php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LeaveRequestService</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">create</span>(<span class="hljs-params">LeaveRequestData $leave</span>): <span class="hljs-title">CreateLeaveRequestResult</span>
    </span>{
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>-&gt;hasConflictedDates($leave)) {
            <span class="hljs-keyword">return</span> CreateLeaveRequestResult::HAS_CONFLICTED_DATES;
        }

        <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">$this</span>-&gt;hasEnoughBalance($leave)) {
            <span class="hljs-keyword">return</span> CreateLeaveRequestResult::INSUFFICIENT_BALANCE;
        }

        <span class="hljs-comment">// all good</span>
        $leaveRequest = <span class="hljs-keyword">$this</span>-&gt;createNewLeaveRequest($leave);

        <span class="hljs-comment">// send notifcations or do anything</span>
        <span class="hljs-comment">// ...</span>

        <span class="hljs-keyword">return</span> CreateLeaveRequestResult::SUCCESS;
    }
}
</code></pre>
<h3 id="heading-response-in-controller">Response in Controller</h3>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">create</span>(<span class="hljs-params">
    LeaveRequestService $service
    SubmitLeaveRequestRequest $request
</span>): <span class="hljs-title">JsonResponse</span> </span>{
    $leave = $request-&gt;getLeaveRequestData();

    $result = $service-&gt;create($leave);
    <span class="hljs-keyword">if</span> ($result !== CreateLeaveRequestResult::SUCCESS) {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JsonResponse([
            <span class="hljs-string">'outcome'</span> =&gt; $result-&gt;name,
        ], <span class="hljs-number">400</span>);
    }

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JsonResponse([
        <span class="hljs-string">'outcome'</span> =&gt; <span class="hljs-string">'SUCCESS'</span>,
    ]);
}
</code></pre>
<p>Awesome right? 😎</p>
<h2 id="heading-pros">PROs</h2>
<ul>
<li><p>Fully typed in your functions.</p>
</li>
<li><p>Better than string/boolean response, especially for complex business logic/flows.</p>
</li>
<li><p>Happier callers, we always know what we'll receive after triggering the functions.</p>
</li>
</ul>
<h2 id="heading-considerations">Considerations</h2>
<h3 id="heading-include-additional-data-when-returning-the-result">Include additional data when returning the result</h3>
<p>Yes, we can, can do either:</p>
<ul>
<li><p>Create a new class which have the Enum result &amp; your additional data</p>
</li>
<li><p>Use my <a target="_blank" href="https://github.com/shipsaas/never-throw"><code>never-throw</code></a> package (written in PHP)</p>
<ul>
<li>Really want to make it as much as same as the TS's neverthrow but still haven't gotten there 🥹.</li>
</ul>
</li>
</ul>
<h3 id="heading-a-bit-of-marketing">A bit of marketing</h3>
<p>As you see in the above example. It relates to the PTO (personal time off) &amp; Vacation tracker.</p>
<p>If your teams want a simple &amp; awesome solution for that, try out <a target="_blank" href="https://flamingoapp.com/">Flamingo App</a> 🦩.</p>
<p>It's lovely, gorgeous, super easy to use, and has an awesome notification system that ensures everybody is in the loop 📣.</p>
<h2 id="heading-conclusions">Conclusions</h2>
<p>Well, that's it guys. I really like this way - making my functions/methods become fully typed.</p>
<p>Happy weekend!</p>
]]></content:encoded></item><item><title><![CDATA[Define TS Types for your Laravel Echo Events]]></title><description><![CDATA[Hey guys,
I've been working on a Laravel project (personal) for fun, focusing on the latest cool stuff: Laravel 11 & Reverb (from Broadcasting) ⭐️.
Using Laravel Broadcasting means you should use the client library provided by Laravel - Laravel Echo,...]]></description><link>https://sethphat.dev/define-ts-types-for-your-laravel-echo-events</link><guid isPermaLink="true">https://sethphat.dev/define-ts-types-for-your-laravel-echo-events</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[laravel-reverb]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Seth Chen]]></dc:creator><pubDate>Sat, 30 Mar 2024 07:12:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1711782686184/b742d4e3-03db-4895-877b-a948b2c9011b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey guys,</p>
<p>I've been working on a Laravel project (personal) for fun, focusing on the latest cool stuff: Laravel 11 &amp; Reverb (from Broadcasting) ⭐️.</p>
<p>Using Laravel Broadcasting means you should use the client library provided by Laravel - Laravel Echo, to save a lot of your time setting up stuff 😎.</p>
<p>Even though Laravel Echo has been written in TypeScript, however, it doesn't give us any opportunity or battery to "type" our events 💣.</p>
<p>So that's why we have this topic and I'll show you how to define the types for your events 😉.</p>
<h2 id="heading-before-going-deeper">Before going deeper</h2>
<p>I suppose you already have some knowledge about:</p>
<ul>
<li><p>TypeScript</p>
</li>
<li><p>Websocket &amp; how it works</p>
</li>
<li><p><a target="_blank" href="https://laravel.com/docs/11.x/broadcasting">Laravel Broadcasting</a></p>
</li>
</ul>
<p>🥹🥹🥹</p>
<h2 id="heading-the-define-types-for-laravel-echo">The Define Types for Laravel Echo</h2>
<p>Note: I'm demonstrating on the <a target="_blank" href="https://laravel.com/docs/11.x/broadcasting#presence-channels">PresenceChannel</a> (private channel with auth &amp; more features).</p>
<h3 id="heading-extending-the-presencechannel">Extending the "PresenceChannel"</h3>
<p>I'll create a file called <code>echo.ts</code> and extend the <code>PresenceChannel</code> interface</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> BaseEvent = {
  <span class="hljs-keyword">type</span>: <span class="hljs-built_in">string</span>;
  payload: unknown;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> StrictPresenceChannel&lt;Event <span class="hljs-keyword">extends</span> BaseEvent&gt;
  <span class="hljs-keyword">extends</span> PresenceChannel {
  listen&lt;EventType <span class="hljs-keyword">extends</span> Event[<span class="hljs-string">'type'</span>]&gt;(
    event: EventType,
    callback: <span class="hljs-function">(<span class="hljs-params">data: Extract&lt;Event, { <span class="hljs-keyword">type</span>: EventType }&gt;[<span class="hljs-string">'payload'</span>]</span>) =&gt;</span> <span class="hljs-built_in">void</span>
  ): <span class="hljs-built_in">this</span>;
}
</code></pre>
<p>This would be our base interface wrapper for the <code>PresenceChannel</code>.</p>
<p>In the real-life app, we would have <em>multiple channels</em> for communicating between client &amp; server.</p>
<p>Each channel should <strong>extend</strong> the <strong>base interface</strong> and have its own Events declaration.</p>
<h3 id="heading-defining-events">Defining Events</h3>
<p>Following the <code>BaseEvent</code> structure, we need to define all of the Events that would be sent to the client.</p>
<p>For a sample chat app, I've added these events:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">type</span> ChatMessageAddedEvent = {
    <span class="hljs-keyword">type</span>: <span class="hljs-string">'ChatMessageAdded'</span>;
    payload: {
        fromUser: {
            id: <span class="hljs-built_in">string</span>; <span class="hljs-comment">// ULID</span>
        },
        chat: {
            id: <span class="hljs-built_in">string</span>;
            message: <span class="hljs-built_in">string</span>;
        }
    }
}

<span class="hljs-keyword">type</span> ChatImageAddedEvent = {
    <span class="hljs-keyword">type</span>: <span class="hljs-string">'ChatImageAdded'</span>;
    payload: {
        fromUser: {
            id: <span class="hljs-built_in">string</span>; <span class="hljs-comment">// ULID</span>
        },
        chat: {
            id: <span class="hljs-built_in">string</span>;
            imageUrl: <span class="hljs-built_in">string</span>;
        }
    }
}

<span class="hljs-keyword">type</span> ChatEvent = ChatMessageAddedEvent | ChatImageAddedEvent;
</code></pre>
<p>Don't forget to add a final type - a union of all events 😉, we'll use it in the part below.</p>
<h3 id="heading-create-a-channel-and-extend-the-strictpresencechannel">Create a Channel and Extend the StrictPresenceChannel</h3>
<p>I created a <code>ChatChannel</code> interface and added the <code>ChatEvent</code> union type.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ChatChannel <span class="hljs-keyword">extends</span> StrictPresenceChannel&lt;ChatEvent&gt; {}
</code></pre>
<h3 id="heading-a-function-to-join-the-typed-channel">A function to join the Typed-Channel</h3>
<pre><code class="lang-typescript"><span class="hljs-comment">// getEchoInstance() is simply returning the new Echo({...})</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getChatChannel = (channelId: <span class="hljs-built_in">string</span>):  =&gt; {
  <span class="hljs-keyword">return</span> getEchoInstance().join(channelId) <span class="hljs-keyword">as</span> ChatChannel;
};
</code></pre>
<p>Here we have to hack a bit - using <code>as</code> to force the type.</p>
<p>This is the shortcut for a <em>quick win</em> &amp; <em>hassle-free</em> while it won't result in any error or failure.</p>
<h3 id="heading-use-the-typed-channel-instance">Use the Typed-Channel instance 😎</h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> channel = getRoomChannel(myChatChannel.id);

channel.listen(<span class="hljs-string">'ChatMessageAdded'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
    <span class="hljs-comment">// access data. &amp; IDE will suggest the fields for you </span>
}).listen(<span class="hljs-string">'ChatImageAdded'</span>, <span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
    <span class="hljs-comment">// access data. &amp; IDE will suggest the fields for you </span>
});
</code></pre>
<p>Then you're all set 😎 IDEs now will handle auto-suggestion, <code>eslint</code>/<code>tsc</code> can do the type check stuff in build time 💪</p>
<p>An example from my project:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711781689197/23324267-4210-4473-992b-5bd4c11f9ad5.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-notes">Notes</h3>
<p>Note: this is purely defining types, it won't guarantee that your data is 100% valid and it could go wrong from the end user 🙀.</p>
<p>I'll make another topic for <strong>schemas definition &amp; data validation</strong> for Laravel Echo soon, ensuring the data is legit to use ⭐️.</p>
<p>Stay tuned 🚀</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Well, that's basically it for defining types for your Broadcast Events 😎.</p>
<p>Have fun guys!</p>
]]></content:encoded></item></channel></rss>