<?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[monospace]]></title><description><![CDATA[KenFai's Personal Technical Blog]]></description><link>https://mono.my</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1605005607173/hfvT2EHe1.png</url><title>monospace</title><link>https://mono.my</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 14 May 2026 07:46:20 GMT</lastBuildDate><atom:link href="https://mono.my/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[4+1 Ways To Store Data In The Web Browser]]></title><description><![CDATA[As the popularity of Single-Page-Application(SPA) rises, the need to store data locally on the client-side becomes increasingly common.
Today, we will examine 4 different methods to store data in a web browser along with basic code examples. We will ...]]></description><link>https://mono.my/4-plus-1-ways-to-store-data-in-the-web-browser</link><guid isPermaLink="true">https://mono.my/4-plus-1-ways-to-store-data-in-the-web-browser</guid><category><![CDATA[Web Development]]></category><category><![CDATA[localstorage]]></category><category><![CDATA[Web API]]></category><category><![CDATA[Browsers]]></category><category><![CDATA[storage]]></category><dc:creator><![CDATA[KenFai]]></dc:creator><pubDate>Sun, 20 Dec 2020 15:06:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1608476725184/VYotAy3rU.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As the popularity of Single-Page-Application(SPA) rises, the need to store data locally on the client-side becomes increasingly common.</p>
<p>Today, we will examine 4 different methods to store data in a web browser along with basic code examples. We will also touch on an upcoming new storage API that is currently in the experimental stage.</p>
<h2 id="introduction">Introduction</h2>
<p>As web applications become increasingly complex, more sophisticated methods to store data locally on the client-side is needed to match with the capabilities of the multitude of server-side storage options available.</p>
<p>Storing data locally also enables offline capability in a web application that helps deliver a smoother user experience for your users even when under poor network condition.</p>
<p>Let's look at the different storage options available in a modern web browser.</p>
<h2 id="1-cookies">1. Cookies</h2>
<p>The most common purpose of storing data locally in web browsers is to retain user session. This has been faithfully served by the use of Cookies ever since the introduction of the Mosaic Netscape browser released in 1994.</p>
<p>To set a cookie, a server needs to return the <code>Set-Cookie</code> in the HTTP response headers as such:</p>
<pre><code>HTTP/2.0 <span class="hljs-number">200</span> OK
<span class="hljs-attribute">Content-Type</span>: text/html
<span class="hljs-attribute">Set-Cookie</span>: app_session=abcde12345
</code></pre><p>Most server-side languages have specific methods to do so. For example, in PHP you could call the <code>setcookie()</code> method:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>
setcookie(<span class="hljs-string">"app_session"</span>, <span class="hljs-string">"abcde12345"</span>);
<span class="hljs-comment">// must be set before any other output</span>
...
</code></pre>
<p>The cookie will then get sent back to the server that matches the domain in subsequent requests to the server.</p>
<p>To retrieve the cookies on the frontend, you can access them with the following JavaScript property:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">document</span>.cookie
<span class="hljs-comment">// will return "app_session=abcde12345;"</span>
</code></pre>
<blockquote>
<p>❗️ Only cookies that are not set with the <code>HttpOnly</code> option is accessible via code on the client-side browser.</p>
</blockquote>
<p>You can also use the same property to set a cookie from the browser:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">document</span>.cookie = <span class="hljs-string">"session_id=abcde12345"</span>;
</code></pre>
<blockquote>
<p>❗️ Please note that multiple cookies with the same name can exist for the same domain path. Your application must be able to determine which value to use.</p>
</blockquote>
<p>However, a cookie can only hold a limited amount of data. In fact, you can only store up to <strong>4096 bytes</strong> of data in a cookie. Not to mention cookies come with an expiration date, just like the Christmas cookies from last year that you are still keeping in your jar.</p>
<p>Cookies also get sent back to the domain server in every request, which may not be necessary for certain types of web applications.</p>
<h2 id="2-localstorage">2. localStorage</h2>
<p>That's when <strong>localStorage</strong> comes to the picture. <code>localStorage</code> provides none of the limitations of a cookie stated above, while at the same time able to persist the data even when the browser was restarted.</p>
<p>With <code>localStorage</code>, you can store up to <strong>5MB</strong> of data in Google Chrome, and they will never expire unless explicitly cleared by the user or through JavaScript code.</p>
<p>You may also be able to save on bandwidth as <code>localStorage</code> does not send its data to the server in every request.</p>
<p>Here's how you can store data into <code>localStorage</code> using JavaScript:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'product_id'</span>, <span class="hljs-number">25</span>);
</code></pre>
<p>You may close the browser window and the data will still persist in the <code>localStorage</code>.</p>
<p>To retrieve the data, simply run the following JavaScript code:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">'product_id'</span>)
<span class="hljs-comment">// will return "25"</span>
</code></pre>
<p>All data save to <code>localStorage</code> will be converted into Strings. Therefore, if you want to store objects, you can use JSON to do so:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'person'</span>, <span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-attr">name</span>: <span class="hljs-string">'John'</span> }));
<span class="hljs-comment">// will be converted into strings as "{"name":"John"}"</span>
</code></pre>
<p>To read the object data, just parse the returned string with JSON again:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">JSON</span>.parse(<span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">'person'</span>))
<span class="hljs-comment">// returns a proper Object type</span>
</code></pre>
<p>You may also perform other operations to the data easily through the following methods:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">localStorage</span>.removeItem(<span class="hljs-string">'product_id'</span>)
<span class="hljs-comment">// remove an item by key</span>

<span class="hljs-built_in">localStorage</span>.clear()
<span class="hljs-comment">// remove all data</span>
</code></pre>
<p><code>localStorage</code> data are bound to the current domain, protocol, and port. So, data between different domain's <code>localStorage</code> space will not interfere with each other.</p>
<p>This also means that if a user has another tab or window opened with the same domain origin, both can read the same data in the domain's <code>localStorage</code>. This shared property is useful in applications where you need to ensure data integrity across all active app instances.</p>
<p>However, if you do not want data to retain after the browser window is closed or to share data across other opened windows, you may consider using <code>sessionStorage</code> instead.</p>
<h2 id="3-sessionstorage">3. sessionStorage</h2>
<p><code>sessionStorage</code> has the same properties and methods with <code>localStorage</code>, but it's limited in two ways.</p>
<p>The first is that the data stored in <code>sessionStorage</code> will only survive a page refresh, but will be gone if the window is closed.</p>
<p>As the name implies, <code>sessionStorage</code> is more useful for application where data is only needed to be retained throughout the current browser session.</p>
<p>The second limitation of <code>sessionStorage</code> is that the data are only available to the current window, and it's not available to another tab or window opened even with the same domain origin.</p>
<p>This is useful in cases where you want your user to have a fresh session every time they load your application site.</p>
<h2 id="4-indexeddb">4. IndexedDB</h2>
<p>If you require a storage solution that is more closely resembles a database, you may consider using the <strong>IndexedDB</strong> storage.</p>
<p>With <strong>IndexedDB</strong>, you can store more than just strings and plain text in a much bigger storage size limit. We are talking about up to 60% of the device's available space. Therefore, storing a few hundred megabytes of data is possible.</p>
<p>IndexedDB even supports transaction, queries, auto-increment, and indexes, just like a database engine that is commonly used on the server-side.</p>
<p>With such powerful capabilities, IndexedDB is best used for building offline web applications that are typically used together with <em>ServiceWorkers</em>.</p>
<p>Let's look at some code on how to store data into IndexedDB.</p>
<h4 id="i-open-a-connection">i) Open a Connection</h4>
<p>Just like a typical database, before we can insert any data, we need to open a connection to it:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// the syntax is indexedDB.open(name, version);</span>
<span class="hljs-keyword">let</span> openRequest = indexedDB.open(<span class="hljs-string">'myLocalDb'</span>, <span class="hljs-number">1</span>);
</code></pre>
<p>Similar to other Web Storage methods mentioned earlier, the database will exist within the current origin domain, protocol, and port. But as you can guess from the code above, you can have many databases within an origin. All you need to do is open a new IndexedDB connection with a different <code>name</code>.</p>
<p>The <code>openRequest</code> is now an object and will emit <code>onsuccess</code>, <code>onerror</code>, and <code>onupgradeneeded</code> events to be listened to subsequently.</p>
<p>The <code>onupgradeneeded</code> will only be triggered if the database has not been created before, or a new <code>version</code> number is introduced.</p>
<h4 id="ii-create-an-object-store">ii) Create an Object Store</h4>
<p>Next, let's create a table or collection. In IndexedDB, this is called an <strong>Object Store</strong>, which can only be created in the <code>onupgradeneeded</code> event handler:</p>
<pre><code class="lang-javascript">openRequest.onupgradeneeded = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">let</span> db = openRequest.result;

    <span class="hljs-comment">// the syntax is createObjectStore(name[, keyOptions]);</span>
    db.createObjectStore(<span class="hljs-string">'fruits'</span>, {<span class="hljs-attr">keyPath</span>: <span class="hljs-string">'id'</span>});
    <span class="hljs-comment">// keyPath is the object property that IndexedDB will use as the key for query later</span>
};
</code></pre>
<p>This makes sense because you don't want to keep running the object store creation script every time your application is launched.</p>
<p>If you want to create or modify an object store, you will have to change the <code>version</code> number when opening a new IndexedDB connection.</p>
<h4 id="iii-storing-data">iii) Storing Data</h4>
<p>Once our object store is available, we can now store some data into it within the <code>onsuccess</code> handler:</p>
<pre><code class="lang-javascript">openRequest.onsuccess = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">let</span> db = openRequest.result;

    <span class="hljs-comment">// prepare the transaction</span>
    <span class="hljs-keyword">let</span> transaction = db.transaction(<span class="hljs-string">'fruits'</span>, <span class="hljs-string">'readwrite'</span>);
    <span class="hljs-comment">// 'readwrite' means we want to perform a write operation</span>

    <span class="hljs-comment">// prepare the object store for use</span>
    <span class="hljs-keyword">let</span> fruits = transaction.objectStore(<span class="hljs-string">'fruits'</span>);

    <span class="hljs-comment">// our data to be stored. Any data type is possible</span>
    <span class="hljs-keyword">let</span> banana = {
        <span class="hljs-attr">id</span>: <span class="hljs-string">'banana'</span>,
        <span class="hljs-attr">price</span>: <span class="hljs-number">12</span>,
        <span class="hljs-attr">created</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
    };

    <span class="hljs-comment">// add the data into the object store</span>
    <span class="hljs-keyword">let</span> request = fruits.add(banana);

    request.onsuccess = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Fruit added successfully"</span>, request.result);
    };

    request.onerror = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Error: "</span>, request.error);
    };
};
</code></pre>
<h4 id="iv-retrieving-data">iv) Retrieving Data</h4>
<p>To retrieve our data, just call the <code>get()</code> method on the object store as such:</p>
<pre><code class="lang-javascript">openRequest.onsuccess = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">let</span> db = openRequest.result;

    <span class="hljs-comment">// prepare the transaction</span>
    <span class="hljs-keyword">let</span> transaction = db.transaction(<span class="hljs-string">'fruits'</span>, <span class="hljs-string">'readonly'</span>);
    <span class="hljs-comment">// 'readonly' indicates we only want to retrieve data</span>

    <span class="hljs-comment">// prepare the object store for use</span>
    <span class="hljs-keyword">let</span> fruits = transaction.objectStore(<span class="hljs-string">'fruits'</span>);

    <span class="hljs-comment">// get the data by the id keyPath</span>
    <span class="hljs-keyword">let</span> request = fruits.get(<span class="hljs-string">'banana'</span>);

    request.onsuccess = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Fruit: "</span>, request.result);
    };

    request.onerror = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Error: "</span>, request.error);
    };
};
</code></pre>
<h4 id="v-deleting-data">v) Deleting Data</h4>
<p>To remove data from the object store, we can use the <code>delete()</code> method:</p>
<pre><code class="lang-javascript">openRequest.onsuccess = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    ...
    <span class="hljs-comment">// similar code as before</span>
    <span class="hljs-keyword">let</span> transaction = db.transaction(<span class="hljs-string">'fruits'</span>, <span class="hljs-string">'readwrite'</span>);
    <span class="hljs-comment">// here we need to specify 'readwrite' because we are modifying data</span>

    <span class="hljs-comment">// delete data by keyPath</span>
    fruits.delete(<span class="hljs-string">'banana'</span>);
    ...

    <span class="hljs-comment">// or delete ALL data in the Object Store</span>
    fruits.clear();
};
</code></pre>
<p>There are many more methods and techniques in using IndexedDB. If you would like to learn more, be sure to check out the links under the Resources section at the bottom.</p>
<h2 id="5-cache-api">5. Cache API</h2>
<p>The <strong>Cache API</strong> is a new storage mechanism that is designed for storing HTTP responses to specific requests that is also commonly used in conjunction with <em>ServiceWorker</em>.</p>
<p>According to MDN, this API is currently under experimental phase. So, it is not recommended to be used in your serious project for now.</p>
<p>However, as a web developer, it's always good to be aware of new tools that are available at our disposal. You may read about it further by checking out the links below.</p>
<h2 id="bonus-viewing-local-storage-data">🎁 Bonus: Viewing Local Storage Data</h2>
<p>As a last tip, while developing your application with local storage, you can always view all the data easily from your browser DevTools.</p>
<p>In Chrome, just press F12 and navigate to <strong>Application</strong> tab:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1608471511803/EWIw4HKy_.png" alt="Screenshot 2020-12-20 at 9.37.12 PM.png" /></p>
<blockquote>
<p>You may ignore the <em>Web SQL</em> data store as it is no longer under active development since 2010</p>
</blockquote>
<p>From here, you can easily browse through all the data in your local store under the current domain. It is also possible to edit and remove the data manually which makes testing your application very convenient.</p>
<h2 id="summary">🏁 Summary</h2>
<p>We have covered all the important local storage options available in most major web browsers.</p>
<p>Each storage methods have different traits that are useful in certain applications. You can always pick the one that best fits your requirements.</p>
<p>🙏 Thank you for reading! I hope you learnt as much as I did. 🚀 If you find this article useful, please share it with your followers. 🦊</p>
<h2 id="resources">📚 Resources</h2>
<ul>
<li>🔗 https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies</li>
<li>🔗 https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API</li>
<li>🔗 https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API</li>
<li>🔗 https://developer.mozilla.org/en-US/docs/Web/API/Cache</li>
<li>🔗 https://javascript.info/data-storage</li>
<li>🔗 https://web.dev/storage-for-the-web/#how-much</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How To Use JavaScript Fetch API For Modern AJAX Requests]]></title><description><![CDATA[In this article, we will look at the use of Fetch API, a modern AJAX request method in JavaScript, as well as the advantages and things to look out for when using it in your project.
Prerequisites
It is recommended that you have some basic experience...]]></description><link>https://mono.my/how-to-use-javascript-fetch-api-for-modern-ajax-requests</link><guid isPermaLink="true">https://mono.my/how-to-use-javascript-fetch-api-for-modern-ajax-requests</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[fetch]]></category><category><![CDATA[Ajax]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[KenFai]]></dc:creator><pubDate>Sun, 13 Dec 2020 16:46:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1607877972188/QNdXbAz8A.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, we will look at the use of <strong>Fetch API</strong>, a modern AJAX request method in JavaScript, as well as the advantages and things to look out for when using it in your project.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>It is recommended that you have some basic experience in using JavaScript and have implemented AJAX on a web project.</p>
<p>We will also touch on a few language features in JavaScript such as arrow functions and Promise. If you don't already know it, this article may serve as a good introduction to start using Promise in your next project.</p>
<h2 id="history">History</h2>
<p>JavaScript has come a long way since its inception in 1995. The web back then was largely static, until <strong>XMLHttpRequest</strong>(XHR) came along around 2002 that really made the web come alive.</p>
<p>However, as with all new technologies, the adoption was slow until  <a target="_blank" href="https://twitter.com/jeresig">John Resig</a>  wrote a wrapper around it in his <strong>jQuery</strong> library back in 2006 which made it easy to implement XHR on any website. That's when <strong>AJAX</strong>, which stands for "Asynchronous JavaScript and XML", becomes one of the most popular and widely used web technologies even until today.</p>
<p>As the internet progresses, platforms and protocols expand. The limitations and flaws of XHR soon surfaced and it deemed inadequate in many modern use cases.</p>
<h2 id="introduction">Introduction</h2>
<p>In 2015, the Fetch API was introduced as a much-improved alternative to XHR. It uses some of the modern language features in JavaScripts such as <strong>Promise</strong>, which provides a much cleaner API and helps avoid callback hell.</p>
<p>In fact, it is so simple that you can make a <code>GET</code> request in a single line of code:</p>
<pre><code class="lang-javascript">fetch(<span class="hljs-string">'https://example.com/books'</span>)
</code></pre>
<p>That's it. In contrast, you will need to call at least three methods on an XHR object just to make a network request. Not to mention the complexity of actually using XHR, such as error handling and multiple event-listeners.</p>
<p>Sure, you could use jQuery and make an AJAX call with a one-liner as well. But that will cost your webpage an extra 70~80kb of traffic and delays just to load the entire jQuery library.</p>
<h2 id="promise">Promise</h2>
<p>Let's look at one of the main advantages of using the Fetch API.</p>
<p>Every <code>fetch()</code> request will return a JavaScript Promise object, which is asynchronous by default. This means you could safely and confidently ensure that your code will only run AFTER a response is successfully returned from a network request.</p>
<p>Assuming we are returning some plain text over our fetch request, we can get the result by using <code>.then()</code> after the fetch:</p>
<pre><code class="lang-javascript">fetch(<span class="hljs-string">'http://example.com/message'</span>)
    .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.text())
    .then(<span class="hljs-function"><span class="hljs-params">text</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(text))
</code></pre>
<p>As you can see, the code is so much simpler and concise compared to using XHR. Also, this is all ready to use in JavaScript without loading any external 3rd party libraries.</p>
<h2 id="response">Response</h2>
<p>The <code>response</code> passed to the <code>.then()</code> function is actually a JavaScript Response object. This means that we could parse a JSON result directly on the Response object without having to read the result first.</p>
<p>Assuming the following request expects a response containing JSON data:</p>
<pre><code class="lang-javascript">fetch(<span class="hljs-string">'https://example.com/users/1'</span>)
    .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
    .then(<span class="hljs-function"><span class="hljs-params">user</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(user.name))
</code></pre>
<p>The response also contains other useful properties that you can read to check the response statuses. Some of the more common ones include:</p>
<p>a) <code>Response.ok</code></p>
<p>Return a Boolean true or false to indicate the success of the response. It will return <code>true</code> for <code>200</code>-<code>299</code> range statuses, and <code>false</code> otherwise.</p>
<p>b) <code>Response.status</code></p>
<p>The status code of the response itself. For example, <code>200</code>, <code>404</code>, <code>500</code> etc.</p>
<p>c) <code>Response.statusText</code></p>
<p>The status message corresponding to the status code. For example, <code>OK</code> for <code>200</code> response.</p>
<p>Here's an example of how to use them:</p>
<pre><code class="lang-javascript">fetch(<span class="hljs-string">'https://example.com/users/1'</span>)
    .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> {
        <span class="hljs-keyword">if</span> (!response.ok &amp;&amp; response.statusText != <span class="hljs-string">'OK'</span>) {
            <span class="hljs-keyword">if</span> (response.status == <span class="hljs-number">404</span>) {
                <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Resource not found!'</span>)
            }
        }
    })
</code></pre>
<h2 id="error-handling">Error Handling</h2>
<p>To handle any connection error, you simply need to add a <code>.catch()</code> method to the chain:</p>
<pre><code class="lang-javascript">fetch(<span class="hljs-string">'https://invalidurl'</span>)
    .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
    ... <span class="hljs-comment">// other .then()</span>
    .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(error))
</code></pre>
<blockquote>
<p>❗️ Fetch only throws an error when the network request itself fails for some reasons, such as unreachable host or broken connection. A <code>500</code> status response(or <code>200</code>, <code>400</code> etc.) is considered a successful request by Fetch, and it should be handled in your response instead.</p>
</blockquote>
<h2 id="post-requests">POST Requests</h2>
<p>All the examples we have seen so far are making only GET requests. Even though we have not set any options for our fetch request, it has accomplished quite a few scenarios.</p>
<p>To send a POST request, we need to set a few options to our <code>fetch()</code> function in the second parameter. Let's look at an example of sending some JSON data over POST.</p>
<pre><code class="lang-javascript">fetch(<span class="hljs-string">'https://example.com/posts'</span>, {
    <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
    <span class="hljs-attr">headers</span>: {
        <span class="hljs-string">'Content-type'</span>: <span class="hljs-string">'application/json; charset=UTF-8'</span>
    },
    <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
        <span class="hljs-attr">title</span>: <span class="hljs-string">'New Post'</span>,
        <span class="hljs-attr">content</span>: <span class="hljs-string">'Hello world!'</span>
    }),
})
.then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response.json())
.then(<span class="hljs-function">(<span class="hljs-params">json</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(json));
</code></pre>
<p>Since the <code>Content-Type</code> header is set to <code>text/plain; charset=UTF-8</code> by default, you will most likely need to provide one according to the content of your request.</p>
<p>For example, for form data request:</p>
<pre><code class="lang-javascript">fetch(<span class="hljs-string">'https://example.com/login'</span>, {
    <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
    <span class="hljs-attr">headers</span>: {
      <span class="hljs-string">'Content-type'</span>: <span class="hljs-string">'application/x-www-form-urlencoded; charset=UTF-8'</span>
    },
    <span class="hljs-attr">body</span>: <span class="hljs-string">'username=admin&amp;password=secret'</span>
})
.then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.text())
.then(<span class="hljs-function"><span class="hljs-params">text</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(text))
</code></pre>
<h2 id="conclusion">🏁 Conclusion</h2>
<p>🎉 That's all you need to know to get started on using Fetch API!</p>
<p>Certainly, there are many more features I have yet to touch on regarding Fetch API. Hopefully, this article will give you a glimpse about the powerful and modern approach towards making AJAX requests using Fetch API compared to XHR. 🚀</p>
<p>To explore further, you may check out the links I shared under the Resources section below.</p>
<p>🙏 Thank you for reading! If you find this article useful, please share it with your followers.</p>
<h2 id="resources">📚 Resources</h2>
<ul>
<li>🔗 https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch</li>
<li>🔗 https://javascript.info/fetch</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How To Setup A PHP Laravel Development Container With Docker]]></title><description><![CDATA[Recently, I've learned to use Docker for my project development environment setup.
A Docker container allows you to contain all the necessary development software and environment needs into one complete package for easy distribution.
By using a devel...]]></description><link>https://mono.my/how-to-setup-a-php-laravel-development-container-with-docker</link><guid isPermaLink="true">https://mono.my/how-to-setup-a-php-laravel-development-container-with-docker</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[MySQL]]></category><category><![CDATA[Docker]]></category><category><![CDATA[containers]]></category><dc:creator><![CDATA[KenFai]]></dc:creator><pubDate>Sun, 06 Dec 2020 13:36:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1607261609034/zAUKyOpow.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently, I've learned to use Docker for my project development environment setup.</p>
<p>A Docker container allows you to contain all the necessary development software and environment needs into one complete package for easy distribution.</p>
<p>By using a development container, anyone in your development team will be able to clone and have the exact same setup ready for development with just one command.</p>
<p>In this article, we will walk through the steps in creating a Docker container for Laravel Development that will include an <strong>NGINX</strong> webserver, <strong>PHP</strong> with extra extensions, as well as <strong>MySQL</strong> database.</p>
<p>In addition, we will throw in <strong> <a target="_blank" href="https://www.adminer.org/">adminer</a> </strong> for easy database management.</p>
<h2 id="prerequisites">🛠 Prerequisites</h2>
<h3 id="docker">Docker</h3>
<p>You need to install Docker on your machine. If you are new to Docker, I recommend you to download Docker Desktop from the following site:</p>
<ul>
<li>🔗 https://www.docker.com/products/docker-desktop</li>
</ul>
<h3 id="git">Git</h3>
<p>Make sure your machine has Git installed.</p>
<h3 id="project-folder">Project Folder</h3>
<p>Before we begin, please create a new folder for your project. All files will be created in this folder.</p>
<p>We will call this folder <code>my-project</code>.</p>
<h2 id="1-clone-or-setup-a-laravel-project">1. Clone or setup a Laravel project</h2>
<p>Make sure you are in the <code>my-project</code> directory.</p>
<pre><code>~$ <span class="hljs-built_in">cd</span> my-project
</code></pre><p>Clone an existing Laravel codebase or set up a new Laravel project.</p>
<pre><code>my-project$ git <span class="hljs-keyword">clone</span> https:<span class="hljs-comment">//github.com/laravel/laravel.git laravel</span>
</code></pre><p>Note that it clones the project files into a folder named <code>laravel</code>.</p>
<h2 id="2-install-composer">2. Install Composer</h2>
<p>Composer is usually required when developing with Laravel.</p>
<p>Instead of forcing your developers to install Composer globally on their machine, we can use Docker’s composer image to mount the directories that are needed by your Laravel project.</p>
<p>Move into the <code>./laravel</code> directory and run the command: </p>
<pre><code>my-project$ cd laravel

laravel$ docker run --rm -v $PWD<span class="hljs-symbol">:/app</span> composer install
</code></pre><p>The <code>-v</code> and <code>--rm</code> flags are used to create a short-lived container that will be bind-mounted to the <code>/laravel</code> directory before being removed. This ensures that the <code>/vendor</code> folder that Composer creates inside the container is copied to your current directory.</p>
<h2 id="3-customize-a-new-php-docker-image">3. Customize a new PHP Docker image</h2>
<p>To be able to run Laravel and Composer, and to connect with MySQL database, we will need to install a few PHP extensions.</p>
<p>We will refer to the official Laravel's requirements here: </p>
<ul>
<li>🔗 https://laravel.com/docs/8.x#server-requirements</li>
</ul>
<p>The official Docker PHP image provides only the bare minimum for most PHP applications. Therefore, to fulfil our requirements, we will have to build our own Docker image.</p>
<p>Move back to <code>my-project</code> folder and create a new folder <code>php-fpm</code>:</p>
<pre><code>laravel$ cd ..

<span class="hljs-keyword">my</span>-project$ <span class="hljs-keyword">mkdir</span> php-fpm
</code></pre><p>Create a new file with the filename <code>Dockerfile</code> inside the new <code>./php-fpm</code> folder:</p>
<pre><code class="lang-Dockerfile"># Use the PHP-FPM 7.4 image
FROM php:7.4-fpm

# Install the necessary software and libraries
RUN apt-get update &amp;&amp; apt-get install -y \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libpng-dev \
    libzip-dev \
    git \
    zip \
    unzip \
    curl

# Install the required PHP extensions
RUN docker-php-ext-install bcmath mysqli pdo_mysql zip
RUN docker-php-ext-configure gd --with-freetype --with-jpeg
RUN docker-php-ext-install -j$(nproc) gd

# Install xdebug
RUN pecl install xdebug \
    &amp;&amp; docker-php-ext-enable xdebug

# Install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
</code></pre>
<p>When saving the file, make sure the <code>Dockerfile</code> filename does not contain any file extension like <code>.txt</code>.</p>
<h2 id="4-setup-nginx-site-configuration">4. Setup NGINX site configuration</h2>
<p>In order for our Laravel application to be served by NGINX webserver, we need to create an NGINX site <code>.conf</code> file.</p>
<p>In <code>\my-project</code> folder, create a new folder <code>nginx</code> and a subdirectory <code>conf.d</code> within the new folder:</p>
<pre><code><span class="hljs-keyword">my</span>-project$ <span class="hljs-keyword">mkdir</span> -p nginx/conf.d
</code></pre><p>The <code>-p</code> flag will allow the creation of subdirectories when the parent directory is not present.</p>
<p>Create a new configuration file <code>site.conf</code> in <code>./nginx/conf.d</code> directory:</p>
<pre><code><span class="hljs-section">server</span> {
    <span class="hljs-attribute">listen</span>      <span class="hljs-number">80</span>;
    <span class="hljs-attribute">server_name</span> localhost;
    <span class="hljs-attribute">root</span>        /var/www/laravel/public;

    <span class="hljs-attribute">add_header</span>  X-Frame-Options <span class="hljs-string">"SAMEORIGIN"</span>;
    <span class="hljs-attribute">add_header</span>  X-XSS-Protection <span class="hljs-string">"1; mode=block"</span>;
    <span class="hljs-attribute">add_header</span>  X-Content-Type-Options <span class="hljs-string">"nosniff"</span>;

    <span class="hljs-attribute">index</span>       index.php;

    <span class="hljs-attribute">charset</span>     utf-<span class="hljs-number">8</span>;

    <span class="hljs-attribute">location</span> / {
        <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$uri</span> <span class="hljs-variable">$uri</span>/ /index.php?<span class="hljs-variable">$query_string</span>;
    }

    <span class="hljs-attribute">location</span> = /favicon.ico { <span class="hljs-attribute">access_log</span> <span class="hljs-literal">off</span>; <span class="hljs-attribute">log_not_found</span> <span class="hljs-literal">off</span>; }
    <span class="hljs-attribute">location</span> = /robots.txt  { <span class="hljs-attribute">access_log</span> <span class="hljs-literal">off</span>; <span class="hljs-attribute">log_not_found</span> <span class="hljs-literal">off</span>; }

    <span class="hljs-attribute">error_page</span> <span class="hljs-number">404</span> /index.php;

    <span class="hljs-comment"># pass the PHP scripts to FastCGI server listening on [server]:9000</span>
    <span class="hljs-attribute">location</span> <span class="hljs-regexp">~ \.php$</span> {
        <span class="hljs-attribute">fastcgi_pass</span>    app:<span class="hljs-number">9000</span>;
        <span class="hljs-attribute">fastcgi_param</span>   SCRIPT_FILENAME <span class="hljs-variable">$realpath_root</span><span class="hljs-variable">$fastcgi_script_name</span>;
        <span class="hljs-attribute">include</span>         fastcgi_params;
    }

    <span class="hljs-attribute">location</span> <span class="hljs-regexp">~ /\.(?!well-known).*</span> {
        <span class="hljs-attribute">deny</span> all;
    }
}
</code></pre><p>The above configuration is based upon the recommended site configuration by Laravel here:</p>
<ul>
<li>🔗 https://laravel.com/docs/8.x/deployment#nginx</li>
</ul>
<p>Please note the following:</p>
<p>i) The <code>root        /var/www/laravel/public;</code> is where we will store our Laravel application codebase.</p>
<p>ii) <code>fastcgi_pass    app:9000;</code> is the Docker network DNS name for the PHP-FPM container, which we will see in the next step.</p>
<h2 id="5-create-a-docker-composeyml-configuration">5. Create a <code>docker-compose.yml</code> configuration</h2>
<p>Instead of running each container service one by one, a <code>docker-compose.yml</code> file will define each container image and setup details that can be called by Docker Compose with a single command.</p>
<p>Let's start by defining the services we need. Create a new <code>docker-compose.yml</code> configuration file in <code>/my-project</code> folder:</p>
<pre><code class="lang-yml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.7"</span>

<span class="hljs-attr">services:</span> 
  <span class="hljs-attr">web:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:latest</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">webserver</span>

  <span class="hljs-attr">app:</span>
    <span class="hljs-attr">build:</span> 
      <span class="hljs-attr">context:</span> <span class="hljs-string">./php-fpm</span>
      <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">php-fpm-laravel</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">app</span>
    <span class="hljs-attr">working_dir:</span> <span class="hljs-string">/var/www/laravel</span>

  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mysql:8.0</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">db</span>

  <span class="hljs-attr">adminer:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">adminer</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">adminer</span>
</code></pre>
<p>Here, we have defined four services in our configuration. As you can guess from the <code>image:</code> field,  each service will serve a different purpose.</p>
<p>This is the recommended practice when setting up container services: Each container should do one thing and do it well.</p>
<p>A few things to take note of the configuration settings:</p>
<p>i) Under the <strong>app</strong> service, we are building a custom PHP Docker image using the build commands in <code>./php-fpm/Dockerfile</code> defined previously.</p>
<p>ii) <code>container_name</code> is important for containers to identify each other within a Docker network.</p>
<p>iii) The <code>working_dir: /var/www/laravel</code> under <strong>app</strong> service will be the location where the Laravel application codebase is stored in the container. This is important when we need to execute PHP Artisan &amp; Composer commands in the container later.</p>
<h2 id="6-mapping-access-port">6. Mapping Access Port</h2>
<p>To access the Laravel <strong>app</strong> &amp; <strong>adminer</strong> from our localhost machine, we need to define each port that maps to the <strong>web</strong> &amp; <strong>adminer</strong> service container.</p>
<p>i) In the <code>docker-compose.yml</code> file, add the following port configuration under the <strong>web</strong> service:</p>
<pre><code class="lang-yml"><span class="hljs-string">...</span>
<span class="hljs-attr">services:</span> 
  <span class="hljs-attr">web:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:latest</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">webserver</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8000:80"</span>
<span class="hljs-string">...</span>
</code></pre>
<p>This means that we will access our Laravel app via http://localhost:8000</p>
<p>ii) Add the following port configuration under the <strong>adminer</strong> service:</p>
<pre><code class="lang-yml"><span class="hljs-string">...</span>
  <span class="hljs-attr">adminer:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:latest</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">webserver</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8888:8080"</span>
</code></pre>
<p>This will allow us to manage our MySQL database using <strong>adminer</strong> from http://localhost:8888</p>
<h2 id="7-set-a-mysql-database-and-password">7. Set a MySQL Database &amp; Password</h2>
<p>Let's set a database and password for MySQL database container.</p>
<p>i) In <code>docker-compose.yml</code> file, add the following <code>environment:</code> configuration under the <strong>db</strong> service:</p>
<pre><code class="lang-yml"><span class="hljs-string">...</span>
<span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mysql:8.0</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">db</span>
    <span class="hljs-attr">environment:</span> 
      <span class="hljs-attr">MYSQL_DATABASE:</span> <span class="hljs-string">laravel</span>
      <span class="hljs-attr">MYSQL_ROOT_PASSWORD:</span> <span class="hljs-string">secret</span>
<span class="hljs-string">...</span>
</code></pre>
<p>ii) Make a copy of <code>./laravel/.env.example</code> file and name the new file <code>./laravel/.env</code>. Then, open the newly copied file <code>./laravel/.env</code> and edit it with the following MySQL Database information:</p>
<pre><code>...
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=secret
...
</code></pre><p>Note that the <code>DB_HOST=db</code> must be equivalent to the <strong>db</strong> service <code>container_name: db</code> from <code>docker-compose.yml</code> file.</p>
<blockquote>
<p>❗️It's not a good practice to use the <code>root</code> account for database access, but it's acceptable for development purposes in a container deployed on your local machine.</p>
</blockquote>
<h2 id="8-setting-up-docker-volumes">8. Setting up Docker Volumes</h2>
<p>At this point, your project directory structure should look like this:</p>
<pre><code><span class="hljs-selector-tag">my-project</span>
    \_ <span class="hljs-selector-tag">laravel</span>
        \_ <span class="hljs-selector-tag">app</span>
        \_ ...
        \_ <span class="hljs-selector-tag">public</span>
        \_ <span class="hljs-selector-tag">vendor</span>
        \_ <span class="hljs-selector-class">.env</span>
        \_ ...
    \_ <span class="hljs-selector-tag">nginx</span>
        \_ <span class="hljs-selector-tag">conf</span><span class="hljs-selector-class">.d</span>
            \_ <span class="hljs-selector-tag">site</span><span class="hljs-selector-class">.conf</span>
    \_ <span class="hljs-selector-tag">php-fpm</span>
        \_ <span class="hljs-selector-tag">Dockerfile</span>
    \_ <span class="hljs-selector-tag">docker-compose</span><span class="hljs-selector-class">.yml</span>
</code></pre><p>Next, we are going to create Docker Volumes for several purposes:</p>
<p>i) First, the Laravel application codebase in our local machine will be mounted to the containers so that any code changes made to our local files will be reflected in the containers without having to reload our containers.</p>
<p>This will speed up the work of your development process.</p>
<p>ii) Second, to mount the NGINX site configuration from <strong>Step 4</strong> before that will override the default site configuration in the NGINX container.</p>
<p>This exposes the site configurations to your development team so that everyone understands how the webserver is being setup.</p>
<p>This also allows us to change our site configuration conveniently at any time without having to do it in the container.</p>
<p>iii) Thirdly, to persist the data stored in MySQL Database so that all data created inside the container database is retained even when the containers are being brought down.</p>
<p>This is useful when we need to develop a huge feature that spans several days and needed the data to persists for development continuation purposes.</p>
<p>Here's the complete <code>docker-compose.yml</code> configuration after adding the volume mounts for each container:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.7"</span>

<span class="hljs-attr">services:</span> 
  <span class="hljs-attr">web:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:latest</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">webserver</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8000:80"</span>
    <span class="hljs-attr">volumes:</span> 
      <span class="hljs-bullet">-</span> <span class="hljs-string">./laravel:/var/www/laravel</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx/conf.d/:/etc/nginx/conf.d/</span>

  <span class="hljs-attr">app:</span>
    <span class="hljs-attr">build:</span> 
      <span class="hljs-attr">context:</span> <span class="hljs-string">./php-fpm</span>
      <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">php-fpm-laravel</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">app</span>
    <span class="hljs-attr">working_dir:</span> <span class="hljs-string">/var/www/laravel</span>
    <span class="hljs-attr">volumes:</span> 
      <span class="hljs-bullet">-</span> <span class="hljs-string">./laravel:/var/www/laravel</span>

  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mysql:8.0</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">db</span>
    <span class="hljs-attr">environment:</span> 
      <span class="hljs-attr">MYSQL_DATABASE:</span> <span class="hljs-string">laravel</span>
      <span class="hljs-attr">MYSQL_ROOT_PASSWORD:</span> <span class="hljs-string">secret</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">laravel-mysql-data:/var/lib/mysql</span>

  <span class="hljs-attr">adminer:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">adminer</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">adminer</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8888:8080"</span>

<span class="hljs-attr">volumes:</span> 
  <span class="hljs-attr">laravel-mysql-data:</span>
</code></pre>
<p>Note the last line <code>laravel-mysql-data:</code> is where the database data will be stored persistently in a Docker volume.</p>
<h2 id="9-run-docker-compose">9. Run Docker Compose</h2>
<p>All the necessary files and configuration is in place. Now let's spin up our containers using Docker Compose.</p>
<p>Make sure you are in the <code>my-project</code> directory where the <code>docker-compose.yml</code> file is located and run the following command:</p>
<pre><code><span class="hljs-keyword">my</span>-project$ docker-compose up -d
</code></pre><p>The <code>-d</code> flag is to instruct Docker to run the containers in detached mode.</p>
<p>Please be patient as the build process will take some time to complete. Once it's completed, you will see the following response in your terminal:</p>
<pre><code>Creating webserver <span class="hljs-keyword">...</span> done
Creating db        <span class="hljs-keyword">...</span> done
Creating adminer   <span class="hljs-keyword">...</span> done
Creating app       <span class="hljs-keyword">...</span> done
my-project$
</code></pre><p>If you have installed Docker Desktop, open the Dashboard to view your new containers:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607245791680/ZUyg2sAbM.png" alt="Screenshot 2020-12-06 at 5.08.44 PM.png" /></p>
<h2 id="10-execute-php-artisan-and-composer-commands">10. Execute PHP Artisan &amp; Composer commands</h2>
<p>Normally, you do not include the <code>.env</code> file and <code>./vendor/</code> folder in your code repository. Therefore, you need to include in your repository <code>README</code> file with the following instructions for your development team on how to run <code>php artisan</code> &amp; <code>composer</code> commands in the containers.</p>
<p>So, before we can access the Laravel application from the browser, we need to run a few commands to set up the necessary data required by Laravel.</p>
<p>i) First, let's generate the application encryption key:</p>
<pre><code>$ docker <span class="hljs-keyword">exec</span> app php artisan key:generate
</code></pre><p>Let's break down the command:</p>
<ul>
<li><code>docker exec</code>: the Docker command to execute commands in a container</li>
<li><code>app</code>: the service <code>container_name</code> defined previously in <code>docker-compose.yml</code> file</li>
<li><code>php artisan key:generate</code>: the actual command you intended to run in the container</li>
</ul>
<p>You will see <code>Application key set successfully.</code> returned for the above successful command.</p>
<p>ii) Next, let's install all composer's dependencies:</p>
<pre><code>$ docker <span class="hljs-keyword">exec</span> app composer install
</code></pre><p>iii) To clean up composer cache:</p>
<pre><code>$ docker <span class="hljs-keyword">exec</span> app composer <span class="hljs-keyword">dump</span>-autoload
</code></pre><p>Now, you can access your application via http://localhost:8000 on a web browser where you will see your Laravel application being loaded.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607258502511/9vdray-gL.png" alt="Screenshot 2020-12-06 at 8.41.16 PM.png" /></p>
<h2 id="11-optional-database-migration-and-adminer-access">11. (Optional) Database migration and <strong>adminer</strong> access</h2>
<p>Next, I'll show you how to install new composer dependencies, run database migration, and view them in <strong>adminer</strong>.</p>
<p>Let's assume your application needs to use the Laravel Passport to handle authentication. You can install the dependencies with the following command:</p>
<pre><code>$ docker <span class="hljs-keyword">exec</span> app composer <span class="hljs-keyword">require</span> laravel/passport
</code></pre><p>To create the tables necessary for Laravel Passport, you may run the following command:</p>
<pre><code>$ docker <span class="hljs-keyword">exec</span> app php artisan migrate
</code></pre><p>And lastly, to create the encryption keys needed to generate secure access tokens in Laravel Passport:</p>
<pre><code>$ docker <span class="hljs-keyword">exec</span> app php artisan passport:install
</code></pre><p>Now, let's inspect the tables and data in MySQL database via <strong>adminer</strong> by going to http://localhost:8888/ using your web browser:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607248739964/AN-h99fDU.png" alt="Screenshot 2020-12-06 at 5.58.08 PM.png" /></p>
<p>Login with the following credentials:</p>
<ul>
<li>Server: <em>db</em></li>
<li>Username: <em>root</em></li>
<li>Password: <em>secret</em></li>
<li>Database: <em>laravel</em></li>
</ul>
<p>You should see the tables and their data created from the migration and installation processes earlier:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1607250394568/7U2fJwGti.png" alt="Screenshot 2020-12-06 at 6.25.32 PM.png" /></p>
<h2 id="12-shutting-down-containers">12. Shutting down containers</h2>
<p>Finally, we can shut down our containers once we are done working on our project.</p>
<pre><code>$ docker-compose down
</code></pre><p>You will see the following response messages in your terminal after the process has completed:</p>
<pre><code>Stopping webserver <span class="hljs-keyword">...</span> done
Stopping app       <span class="hljs-keyword">...</span> done
Stopping adminer   <span class="hljs-keyword">...</span> done
Stopping db        <span class="hljs-keyword">...</span> done
Removing webserver <span class="hljs-keyword">...</span> done
Removing app       <span class="hljs-keyword">...</span> done
Removing adminer   <span class="hljs-keyword">...</span> done
Removing db        <span class="hljs-keyword">...</span> done
Removing network my-project_default
my-project$
</code></pre><p>Alternatively, take a look at the Docker Desktop Dashboard. You will find that your project containers are gone from the list.</p>
<hr />

<h2 id="summary">🏁 Summary</h2>
<p>🎉 That's all for today! We have come to an end to the walkthrough of setting up a Laravel Development Container using Docker.</p>
<p>With this method, we can say goodbye to setting up a different development environment manually on your machine for every project. Your development team will no longer have to spend the whole day setting up their machine before they can start working on the project. Not to mention having to make sure all software and environments must match the project system requirements.</p>
<p>Hopefully, by learning how to use containers as a development environment, you will no longer hear your developers saying "It works on my machine!". 😉</p>
<p>🙏 Thank you for reading! If you find this article useful, please share it with your followers.</p>
<p>💬 If you have any questions, feel free to leave a comment below.</p>
<h2 id="references">📚 References</h2>
<ul>
<li>https://www.docker.com/101-tutorial</li>
<li>https://www.digitalocean.com/community/tutorials/how-to-set-up-laravel-nginx-and-mysql-with-docker-compose</li>
<li>https://hub.docker.com/_/composer</li>
<li>https://hub.docker.com/_/php</li>
<li>https://laravel.com/docs/8.x/installation#server-requirements</li>
<li>https://laravel.com/docs/8.x/deployment#nginx</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How I Passed My AWS Certified Cloud Practitioner Exam]]></title><description><![CDATA[Last week, I've managed to pass my AWS Certified Cloud Practitioner exam. To be exact, I scored 974 over 1000, where the minimum score needed to pass the exam is 700.

🎖 https://www.youracclaim.com/badges/00afda49-dfd8-45e1-bdd7-dec12965f2b1

In thi...]]></description><link>https://mono.my/how-i-passed-my-aws-certified-cloud-practitioner-exam</link><guid isPermaLink="true">https://mono.my/how-i-passed-my-aws-certified-cloud-practitioner-exam</guid><category><![CDATA[AWS]]></category><category><![CDATA[Cloud Computing]]></category><category><![CDATA[Certification]]></category><dc:creator><![CDATA[KenFai]]></dc:creator><pubDate>Sun, 29 Nov 2020 15:59:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1606663493178/BMkD_3ENw.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Last week, I've managed to pass my <strong>AWS Certified Cloud Practitioner</strong> exam. To be exact, I scored 974 over 1000, where the minimum score needed to pass the exam is 700.</p>
<ul>
<li>🎖 https://www.youracclaim.com/badges/00afda49-dfd8-45e1-bdd7-dec12965f2b1</li>
</ul>
<p>In this article, I would like to share some tips as well as the methods &amp; <em>FREE</em> resources that I have used to study for the AWS Cloud Practitioner exam and passed.</p>
<p>This is the first IT certification that I have taken ever since I graduated from University almost a decade ago. Hence, if you have never taken any certification or exam, you may be able to relate to my experience here.</p>
<blockquote>
<p>❗️Do keep in mind that your mileage may vary. What works for me may not work for you.</p>
<p>❗️Disclaimer: I have no affiliation with all the links that I shared in this article.</p>
</blockquote>
<h2 id="prerequisite">Prerequisite</h2>
<h4 id="a-technical-background">A) Technical Background</h4>
<p>I need to clarify that I did not learn from scratch. I was (un)fortunate enough to be involved in a past client e-Commerce project that needed to be deployed on AWS infrastructure. However, only minimal efforts were carried out in that one-time experience.</p>
<p>It is also due to that experience of not knowing how to work on cloud correctly that led me to discover about AWS Certification paths.</p>
<p>Although having a technical background does help, but there aren't really any background requirements in getting started with AWS. </p>
<h4 id="b-exam-requirements">B) Exam Requirements</h4>
<p>This exam is entirely optional. In fact, you could skip this and go straight for the AWS Certified Solutions Architect Associate exam.</p>
<p>However, it's highly recommended that you take the Cloud Practitioner exam first, mainly for two valid reasons:</p>
<ol>
<li><p>If you have minimal or no experience in using AWS Cloud services, it is good to familiarise yourself not only with AWS and general cloud computing concept before diving into a much deeper area, but also with how the exam works and the mechanism of the exams. The latter is important if you have been out of school for a very long time, or have never taken any exams before.</p>
</li>
<li><p>The cost for the Cloud Practitioner exam is $100 USD, while the cost for Solutions Architect Associate exam is $150 USD. BUT, whenever you <em>passed</em> ANY exams on AWS, you will be given a <strong>50%</strong> voucher for your next exam as part of the certification benefits!</p>
</li>
</ol>
<p>If you do the math, taking both exams will cost you $100 + $75(50%) = $175 USD. But taking only the Solutions Architect Associate exam will already costing you $150 USD. Might as well spend another +$25 to get both certificates. ¯_(ツ)_/¯</p>
<h4 id="c-physical-notebook-for-note-taking">C) Physical Notebook for Note Taking</h4>
<p>Call me old-fashioned, but a physical notebook still works best for me when it comes to note-taking. The act of writing down actually reinforces the knowledge that you learned.</p>
<h4 id="d-register-for-a-free-tier-aws-console-account">D) Register For a FREE-tier AWS Console Account</h4>
<p>Nothing beats doing when it comes to learning. Please register for a FREE-tier account on AWS so that you can experiment with the AWS Console throughout your learning.</p>
<ul>
<li>🔗 https://console.aws.amazon.com/</li>
</ul>
<p>It will cost you nothing in the first year if you are just experimenting.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606664091257/NthDVqNGL.png" alt="Screenshot 2020-11-29 at 11.33.49 PM.png" /></p>
<h2 id="1-official-exam-guide">1. Official Exam Guide</h2>
<p>Before you start studying, you need to know the scope of the exam. AWS provides a clear but slightly vague Exam Guide for anyone who planned to take the exam.</p>
<ul>
<li>📄 https://d1.awsstatic.com/training-and-certification/docs-cloud-practitioner/AWS-Certified-Cloud-Practitioner_Exam-Guide.pdf</li>
</ul>
<p>Read through it but you don't have to know everything that's mentioned in the guide. You will come back to this guide when you are ready to register for the exam again.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606664197344/ZE0hvH7AN.png" alt="Screenshot 2020-11-29 at 11.36.12 PM.png" /></p>
<p>The only thing that's not mentioned in the Exam Guide is that there are 65 questions in the exam. Since they did not mention it in the guide, it means that this could change in the future.</p>
<h2 id="2-official-digital-classroom-lesson">2. Official Digital Classroom Lesson</h2>
<p>Sign up for an account on https://aws.training and enrol yourself in this free Digital Training course:</p>
<ul>
<li>🔗 https://www.aws.training/Details/eLearning?id=60697</li>
</ul>
<p>This AWS Cloud Practitioner Essentials course has been updated recently and I found it to be very high quality. According to the course description, this course also helps you prepare for the AWS Certified Cloud Practitioner exam.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606663841610/ixrgABjf8.png" alt="Screenshot 2020-11-29 at 11.29.13 PM.png" /></p>
<p>The format of the training consists of instructor-led video lessons, text summaries, and an interactive section for knowledge check at the end of each lesson. It is definitely a good starting place to begin your cloud exploration.</p>
<p>This is where I began taking note of key concepts and important learning points in my notebook as well.</p>
<p>After completing this course, you should have a good overview of AWS.</p>
<h2 id="3-freecodecamporg-exampro">3. freeCodeCamp.org / ExamPro</h2>
<p>Watch this video:</p>
<ul>
<li>▶️ https://www.youtube.com/watch?v=3hLmDS179YE</li>
</ul>
<p>This 4-hour video is a must-watch for anyone planning to take and pass the Cloud Practitioner exam. It is a complete course presented by the amazing Andrew Brown(https://twitter.com/andrewbrown) from ExamPro for FREE.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606664441349/15ahIJAhl.png" alt="Screenshot 2020-11-29 at 11.39.29 PM.png" /></p>
<p>The video is very concise and packed with all the knowledge you need in passing the exam. It covers the essentials as well as provides hands-on practices on key services. Furthermore, all services are introduced and similar services are carefully compared to make sure you do not confuse with them.</p>
<p>After all, there are more than 140+ services offered by AWS. Therefore, it is crucial that you know what each of them does and the difference from each other.</p>
<p>Pace yourself and do make sure that you continue to do note-taking, or best if you could draw out mindmaps to aid in your understanding and revision later.</p>
<h2 id="4-whitepapers-and-documentations">4. Whitepapers and Documentations</h2>
<p>Here comes the "boring" but one of the most important parts of your study. </p>
<p>a) Please download and read the following Whitepapers from AWS:</p>
<ul>
<li>📄 https://d1.awsstatic.com/whitepapers/aws-overview.pdf</li>
<li>📄 http://d1.awsstatic.com/whitepapers/aws_pricing_overview.pdf</li>
</ul>
<p>b) Please make sure you know by heart on the following topic as well:</p>
<ul>
<li>🔗 https://aws.amazon.com/premiumsupport/plans/</li>
</ul>
<p>The videos might be great as an introductory to AWS &amp; Cloud Computing concepts in general. However, to really learn about the details, this is where you need to dive a little deeper to make sure you get all the specifics right.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606664539818/kxpovDqs9.png" alt="Screenshot 2020-11-29 at 11.41.46 PM.png" /></p>
<p>Not to mention AWS is constantly evolving, so the videos might miss out some of the latest updates that have been introduced recently.</p>
<p>It is recommended to write a short one-sentence summary in your own words of every single AWS service from the whitepaper in your notebook. This has helped me in making sure I understand what each service do, and to provide a go-to reference point whenever you want to revise on them.</p>
<p>In addition to the whitepapers, you will most likely need to Google to lookup on some key concepts that you want to clarify further. More often than not, you will always land on none other than the <strong>AWS Official Documentation</strong> page for a particular service itself.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606664655438/_nRwbHOd4.png" alt="Screenshot 2020-11-29 at 11.43.36 PM.png" /></p>
<p>This is where you need to familiarise yourself on navigating and reading the official documentation. I believe you will be depending on the information in the official documentation regularly throughout your cloud journey.</p>
<h2 id="5-aws-online-conferences-and-events">5. AWS Online Conferences &amp; Events</h2>
<p>Keep an eye on any online conference organized by AWS. They do not happen often, but I take it as a good medium to break out from reading the whitepapers.</p>
<p>For example, here's the upcoming <strong>AWS re:Invent</strong> FREE Virtual Conference that you can sign up and watch:</p>
<ul>
<li>🔗 https://reinvent.awsevents.com/index.html</li>
</ul>
<p>The conferences are usually FREE and presented by AWS technical experts. In the conference, AWS technical experts will explain key features and use cases, share best practices, and walk through technical demos.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606664753066/qpee7FE6K.png" alt="Screenshot 2020-11-29 at 11.45.15 PM.png" /></p>
<p>Depending on the event, if you managed to attend the sessions live, they might be available to answer your questions one-on-one.</p>
<h2 id="6-practice-exam-questions">6. Practice Exam Questions</h2>
<p>In most Asian education culture, exams are everything. Students are trained to perform well in exams. The best way to excel in your exam is to practise on the exam questions itself.</p>
<p>Therefore, try to search for any FREE practice exam questions you could find on the internet. Many websites actually have exam questions dump with answers from various sources.</p>
<h4 id="a-here-are-a-few-that-ive-used">a) Here are a few that I've used:</h4>
<ul>
<li>🔗 https://free-braindumps.com/amazon/free-aws-certified-cloud-practitioner-braindumps.html</li>
<li>🔗 https://www.itexams.com/exam/AWS%20Certified%20Cloud%20Practitioner</li>
</ul>
<h4 id="b-not-tried-but-seems-to-be-good-quality">b) Not tried, but seems to be good quality:</h4>
<ul>
<li>🔗 https://www.awsboy.com/aws-practice-exams/practitioner/</li>
<li>🔗 https://awslagi.com/practice-questions/</li>
</ul>
<h4 id="c-from-official-aws">c) From official AWS:</h4>
<p><strong>GOOD News!</strong> As part of the <strong>Get AWS Certified Global Challenge</strong>, AWS is giving out FREE practice exam voucher to anyone who signs up for the challenge. You may request for a practice exam voucher here:</p>
<ul>
<li>🔗 https://pages.awscloud.com/AWS-Global-Certification-Challenge-Practice-Exam-Request.html</li>
</ul>
<blockquote>
<p>❗️Offer is valid until 31st December 2020, so make sure you take action immediately.</p>
</blockquote>
<p>Again, I cannot stress enough how important is it to practise on exam questions. If you are facing time constraints, this is the only area you should put your effort in to increase your chances in passing the exam.</p>
<p>As soon as you start doing exam questions, you will quickly realise there are still many details in your learnings that you may have missed out. You will also discover common gotchas and edge scenarios that you have never considered before.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606665302964/jbMa_y5wg.png" alt="Screenshot 2020-11-29 at 11.54.15 PM.png" /></p>
<p>For every question that you answered wrongly, please Google about it and make sure you understand the correct answer and why the mistake was made. Read the official documentation if you need to, as it is the source of truth.</p>
<h2 id="7-cheat-sheet">7. Cheat Sheet</h2>
<p>Once you have done all the above, it is time for a final round of revision before you take the exam.</p>
<p>Your notebook that you have been taking notes with should now be served as a revision guide before you sit for the exam.</p>
<p>In addition, you should also review some of the cheat sheets out there to make sure you have absolutely covered everything that is required to know to pass the exam.</p>
<ul>
<li>🔗 https://digitalcloud.training/certification-training/aws-certified-cloud-practitioner/</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606664889167/9pwZHUddZ.png" alt="Screenshot 2020-11-29 at 11.47.19 PM.png" /></p>
<p>Lastly, it is also a good time to take a look at the Cloud Practitioner Exam Guide again, to make sure you have covered all topics.</p>
<h2 id="extras">🎁 Extras</h2>
<h3 id="looking-for-help">Looking For Help</h3>
<p>No doubt you will have questions during your studies. If you can't find the answer you need everywhere, you may need to ask someone who has the answer.</p>
<p>For this purpose, you may ask for help from the cloud community on <strong>A CLOUD GURU</strong> forum:</p>
<ul>
<li>🔗 https://acloud.guru/forums</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606665029558/2ZY1UqDhA.png" alt="Screenshot 2020-11-29 at 11.49.09 PM.png" /></p>
<p>Very likely, any question that you might have has already been asked and answered by someone else. You just need to search in the forum using the right keyword. If not, just sign up for an account and post your questions there.</p>
<h3 id="scheduling-and-taking-the-exam">Scheduling And Taking The Exam</h3>
<p>At this point, you should be confident enough to schedule a date for the exam.</p>
<blockquote>
<p>❗️Due to COVID-19 restrictions around the world, AWS allows all exams to be taken through online proctored from your home or office, without having to take them at an exam centre. If you choose so, please be certain of the rules that you need to adhere to.</p>
</blockquote>
<p>For your information, I chose to take my exam from home via online proctored managed by PearsonVUE using their OnVUE application which they provided for candidates. I used an Apple MacBook Pro with working webcam &amp; microphone while connected to my home WiFi fibre internet connection to complete the test.</p>
<p>On your exam day, make sure you are well rested the night before and try not to drink too much fluid, as breaks are not allowed throughout the <strong>90 minutes</strong> exam session.</p>
<p>While answering the questions, you may not scribble on any notes or paper. However, it is possible to flag a question and return to it at a later time.</p>
<p>Do not spend too much time on a single question. If you get stuck and do not know the answer, select the most likely answer or take a guess, <strong>flag</strong> it, and move on to the next question.</p>
<p>If you are lucky, another question further down might give you a hint or the answer right out to the previous question that you had trouble figuring out before. The point here is that you should try your best to answer <em>ALL</em> 65 questions. It is not worth to leave any unanswered question.</p>
<p>In my exam, I spent a total of 65 minutes before ending my exam session. During which I had around 10 questions flagged, and managed to review all the questions once more after completing them the first round.</p>
<p>You will know if you have passed or failed immediately upon ending your exam. However, you will only receive the official certification within 5 business days.</p>
<h2 id="conclusion">🏁 Conclusion</h2>
<p>That's all I have to share about my experience in preparing and passing the AWS Certified Cloud Practitioner exam.</p>
<p>🙏 Thank you for reading and best of luck in your exam! 🍀 I hope my experience will help you succeed in getting your AWS Certified Cloud Practitioner certification and beyond. 🚀☁️</p>
<h2 id="resources">📚 Resources</h2>
<ul>
<li>https://aws.amazon.com/certification/certification-prep/</li>
<li>https://pages.awscloud.com/takethechallenge.html (due date 31 December 2020)</li>
<li>https://www.aws.training/</li>
<li>https://www.youtube.com/watch?v=3hLmDS179YE</li>
<li>https://aws.amazon.com/events/</li>
<li>https://free-braindumps.com/amazon/free-aws-certified-cloud-practitioner-braindumps.html</li>
<li>https://www.itexams.com/exam/AWS%20Certified%20Cloud%20Practitioner</li>
<li>https://www.awsboy.com/aws-practice-exams/practitioner/</li>
<li>https://awslagi.com/practice-questions/</li>
<li>https://digitalcloud.training/certification-training/aws-certified-cloud-practitioner/</li>
<li>https://acloud.guru/forums</li>
<li>https://home.pearsonvue.com/Clients/Amazon-Web-Services/Online-Proctored.aspx</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How To Setup Dynamic DNS in Localhost For Public Access]]></title><description><![CDATA[If you ever find yourself having the need to temporarily allow your client to access a website that you are building for them in your localhost, you can use a FREE Dynamic DNS service to do so.
Dynamic DNS is also useful if you are testing some 3rd p...]]></description><link>https://mono.my/how-to-setup-dynamic-dns-in-localhost-for-public-access</link><guid isPermaLink="true">https://mono.my/how-to-setup-dynamic-dns-in-localhost-for-public-access</guid><category><![CDATA[networking]]></category><category><![CDATA[Web Hosting]]></category><dc:creator><![CDATA[KenFai]]></dc:creator><pubDate>Sun, 22 Nov 2020 15:33:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1606059078503/zHmmwfNBV.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you ever find yourself having the need to temporarily allow your client to access a website that you are building for them in your localhost, you can use a FREE Dynamic DNS service to do so.</p>
<p>Dynamic DNS is also useful if you are testing some 3rd party callback services such as Payment Gateway and requiring a response endpoint but you are still developing the app on your localhost and have not registered for a domain name yet.</p>
<p>The issue with your home or office internet is that you are usually provided with a dynamic IP address by your internet service provider(ISP) that can change anytime. Thus, it is not practical to provide this IP address to your client when they ask to check your work progress, or to use it as a 3rd party service callback response endpoint during development.</p>
<hr />

<h1 id="what-is-dynamic-dns">🌐 What is Dynamic DNS?</h1>
<p>The above issue can be solved by setting up a Dynamic DNS on your router.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606055639566/H9MRfxztc.png" alt="dynamic-dns.png" /></p>
<p>Dynamic DNS allows you to assign a fixed domain name to a dynamic IP address. Even when the dynamic IP address assigned to you changes, the Dynamic DNS service will automatically update the Domain Name Server(DNS) to point to your new IP address.</p>
<p>❗️ However, do note that it is <strong>NOT</strong> advisable to use Dynamic DNS for any production system or live website. Not only this may violate the terms with your ISP, but your home internet connection may not have adequate uploading bandwidth to handle the incoming traffic. Not to mention your computer may not be powerful enough to handle high volume of requests as well.</p>
<h2 id="prerequisite">🖇 Prerequisite</h2>
<h3 id="router-access">Router Access</h3>
<p>Most modern routers come with Dynamic DNS feature built-in. But you do need to have the administrator access to your router in order to configure it.</p>
<p>In this guide, I will use a <em>TP-LINK Wireless Router Archer C1200</em> model as an example. But rest assured that most routers have similar settings and keywords, and guides for specific router models can be easily found on the internet elsewhere.</p>
<p>I have put up some links to a few popular router brands and their DDNS guide under the Reference section at the bottom.</p>
<h2 id="step-1-configure-dynamic-dns-on-your-router">Step 1: Configure Dynamic DNS On Your Router</h2>
<p>a) Find out your router IP address. Usually, it can be accessed by going to <em>http://192.168.0.1</em> via your web browser.</p>
<p>If not, you may locate from your computer's Network settings. On macOS, navigate to <strong>System Preferences</strong> &gt; <strong>Network</strong> &gt; <strong>WiFi</strong> or the connected Internet connection &gt; click on the <strong>Advanced...</strong> button &gt; click on the <strong>TCP/IP</strong> tab &gt; look for the <strong>Router</strong> value:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606043462671/euulx9N9Y.png" alt="Screenshot 2020-11-22 at 7.08.54 PM.png" /></p>
<blockquote>
<p>❗️ Please note down the <strong>IPv4 Address</strong> as well, as we will need this value later on.</p>
</blockquote>
<p>b) Enter the Router IP address to your web browser location and you should arrive at a login page for your router. Login to your router configuration panel with a username and password. For TP-LINK, the default credentials are <em>admin/admin</em>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606043621580/9ghPRFs3a.png" alt="Screenshot 2020-11-22 at 7.13.25 PM.png" /></p>
<p>c) Once logged in, navigate to <strong>Advanced</strong> &gt; <strong>Network</strong> &gt; <strong>Dynamic DNS</strong>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606044041815/V2bMHYVqs.png" alt="Screenshot 2020-11-22 at 7.20.00 PM.png" /></p>
<p>Select "NO-IP" and click on "Go to register..." link to register a FREE NO-IP account.</p>
<p>d) On the noip.com page, enter a <strong>Hostname</strong> and select a domain name from the drop-down list.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606044306478/rewOeFyuk.png" alt="Screenshot 2020-11-22 at 7.23.51 PM.png" /></p>
<p>Click on the <strong>Sign Up</strong> button once you have made your selection.</p>
<p>e) Complete the Free Sign Up form on the next page. You will most likely need to confirm your email address to activate your free account.</p>
<blockquote>
<p>❗️ Do note that your Free Domain Name will expire in 30 days.</p>
</blockquote>
<p>f) Return to your Router Dynamic DNS configuration page and enter your noip.com account details and the full Domain Name:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606047703274/3WfpHpwbb.png" alt="Screenshot 2020-11-22 at 8.19.40 PM.png" /></p>
<p>g) Click on the <strong>Login and Save</strong> button to save your settings. At this point, your router will attempt to connect to the NO-IP DDNS service with your account details.</p>
<p>You will see a "Success!" message if it's successful:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606047818490/2fqSQTWei.png" alt="Screenshot 2020-11-22 at 8.21.16 PM.png" /></p>
<h2 id="step-2-add-port-forwarding-to-http">Step 2: Add Port Forwarding to HTTP</h2>
<p>To allow incoming requests from the internet to your local network via HTTP, you need to enable <strong>Port Forwarding</strong> on your router, so that your router will pass on all HTTP traffic to your local computer correctly.</p>
<p>You may visit https://portforward.com/ to look for a suitable guide specific to your Router brand and model.</p>
<p>a) On your Router configuration page, navigate to <strong>Advanced</strong> &gt; <strong>NAT Forwarding</strong> &gt; <strong>Virtual Servers</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606051024468/7EJQe5zhF.png" alt="Screenshot 2020-11-22 at 9.11.49 PM.png" /></p>
<p>b) Click on "Add" to add a new entry:</p>
<ul>
<li><strong>Service Type</strong>: Enter "HTTP" or click on the "View Existing Services" and search for <em>HTTP</em>.</li>
<li><strong>External Port</strong>: 80</li>
<li><strong>Internal IP</strong>: 192.168.0.126 (Your local computer IPv4 Address, from <strong>Step 1 a)</strong> above)</li>
<li><strong>Internal Port</strong>: 80</li>
<li><strong>Protocol</strong>: TCP</li>
<li>Check <strong>Enable This Entry</strong></li>
</ul>
<p>c) Click <strong>OK</strong> to save the entry. You should have a new entry as such:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606051278204/bVndMjeNz.png" alt="Screenshot 2020-11-22 at 9.21.13 PM.png" /></p>
<h2 id="step-3-add-domain-name-to-webserver">Step 3: Add Domain Name to Webserver</h2>
<p>In order for the domain name to reach your website, you will need to inform your localhost Webserver on the new domain name.</p>
<p>a) For NGINX webserver, locate your site configuration file and add the domain name to the <code>server_name</code> directive:</p>
<pre><code>server {
    listen <span class="hljs-number">80</span>;
    server_name localhost mysuperapp.ddns.net;
    root /<span class="hljs-keyword">var</span>/www/html;

    ...
}
</code></pre><p>Save and reload NGINX to load the changes:</p>
<pre><code>$ sudo nginx -s reload
</code></pre><p>b) For Apache webserver, locate your site or virtual host configuration file and add the domain name to the <code>ServerAlias</code> directive:</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">VirtualHost</span> *<span class="hljs-attr">:80</span>&gt;</span>
    ServerName localhost
    ServerAlias mysuperapp.ddns.net

    ...
<span class="hljs-tag">&lt;/<span class="hljs-name">VirtualHost</span>&gt;</span>
</code></pre><p>Save and restart Apache:</p>
<pre><code>$ sudo apachectl -k <span class="hljs-keyword">restart</span>
</code></pre><h2 id="step-4-add-domain-name-to-hosts-file">Step 4: Add Domain Name to Hosts File</h2>
<p>In order for the Domain Name to reach your computer, you need to add the Domain Name to the collection of hosts in your computer <strong>hosts</strong> file.</p>
<p>a) On macOS and most Linux OS, that file can be located in <code>/etc/hosts</code>. You will require administrative privilege to edit the file:</p>
<pre><code>$ sudo vim /etc/hosts
</code></pre><p>b) On Windows, the <em>hosts</em> file can be located in <code>C:\Windows\System32\drivers\etc\hosts</code>.</p>
<p>c) <strong>DO NOT</strong> remove any entries in this file. Instead, just add a new entry with a new line at the end of the file:</p>
<pre><code>127<span class="hljs-selector-class">.0</span><span class="hljs-selector-class">.0</span><span class="hljs-selector-class">.1</span>            <span class="hljs-selector-tag">localhost</span>
...
192<span class="hljs-selector-class">.168</span><span class="hljs-selector-class">.0</span><span class="hljs-selector-class">.126</span>        <span class="hljs-selector-tag">mysuperapp</span><span class="hljs-selector-class">.ddns</span><span class="hljs-selector-class">.net</span>
</code></pre><p>The format should be: <code>[your computer IPv4 Address] [domain name]</code>, where IPv4 Address value can be found in the Network settings in <strong>Step 1 a)</strong> above.</p>
<p>d) Save the file.</p>
<h2 id="step-5-allow-incoming-connections-on-firewall">Step 5: Allow Incoming Connections on Firewall</h2>
<p>Depending on your Operating System, you may need to configure your Firewall to allow incoming connections to your Webserver.</p>
<p>a) On macOS, navigate to <strong>System Preferences</strong> &gt; <strong>Security &amp; Privacy</strong> &gt; <strong>Firewall</strong> &gt; <strong>Firewall Options</strong> &gt; <strong>Add New</strong> entry for your Webserver(e.g. NGINX or Apache) &gt; Set it to "Allow incoming connections":</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1606051685830/_QFBwFgR9.png" alt="Screenshot 2020-11-22 at 9.23.41 PM.png" /></p>
<p>b) Click <strong>Ok</strong> to save the new Firewall entry.</p>
<h2 id="complete">🏁 Complete</h2>
<p>🎉 That's it!</p>
<p>Now you can access your localhost website by using a FREE Domain Name. 🙌</p>
<p>You may verify the public access to your localhost website by using another internet connection such as your mobile data network. Enter the domain name into your web browser to load and view your localhost website.</p>
<p>You may now share this domain name to your client for them to access it from their end, or use this domain name as an endpoint to any 3rd party response callback service that you are testing.</p>
<hr />

<p>❗️ Thank you for reading! I hope you learnt something new today. If you find this useful, feel free to share it with your followers. 🦊</p>
<h2 id="reference">📚 Reference</h2>
<h4 id="dynamic-dns-configuration-for-major-router-brands">Dynamic DNS Configuration for major Router brands:</h4>
<ul>
<li>TP-LINK: https://www.tp-link.com/us/support/faq/1367/</li>
<li>D-Link: https://eu.dlink.com/uk/en/support/faq/cameras-and-surveillance/mydlink/settings/router/how-do-i-configure-dynamic-dns-on-my-dir-series-router</li>
<li>NETGEAR: https://kb.netgear.com/23860/How-do-I-set-up-a-NETGEAR-Dynamic-DNS-account-on-my-NETGEAR-Nighthawk-router</li>
<li>Cisco-Linksys: https://www.linksys.com/us/support-article?articleNum=142585</li>
</ul>
<h4 id="port-forwarding-configuration-for-most-major-router-brands">Port Forwarding Configuration for most major Router brands:</h4>
<ul>
<li>https://portforward.com/</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Hosting a Static Site On AWS S3 FREE Tier]]></title><description><![CDATA[Today we are going to walk through the steps required in setting up a static site on Amazon S3 and served via Amazon CloudFront CDN under the FREE tier.
This guide is suitable for anyone who is new to AWS and would like to try it out without having t...]]></description><link>https://mono.my/hosting-a-static-site-on-aws-s3-free-tier</link><guid isPermaLink="true">https://mono.my/hosting-a-static-site-on-aws-s3-free-tier</guid><category><![CDATA[AWS]]></category><category><![CDATA[Amazon S3]]></category><category><![CDATA[static]]></category><category><![CDATA[Web Hosting]]></category><dc:creator><![CDATA[KenFai]]></dc:creator><pubDate>Sun, 15 Nov 2020 17:50:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1605462564922/ULorl3Hj9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today we are going to walk through the steps required in setting up a static site on <strong>Amazon S3</strong> and served via <strong>Amazon CloudFront</strong> CDN under the <em>FREE</em> tier.</p>
<p>This guide is suitable for anyone who is new to AWS and would like to try it out without having to provision any servers or database.</p>
<h2 id="introduction">☁️ Introduction</h2>
<h4 id="static-site">Static Site</h4>
<p>A static site is just like any regular website. In fact, it is what web pages supposed to be like in its most fundamental form since the beginning of the web.</p>
<p>As the name implies, a static site has no dynamic component. A static site is usually made up of plain HTML files, CSS stylesheets, images, as well as Javascript files. It has no capabilities to perform any dynamic request or backend processing, such as processing form data or retrieving information from the database.</p>
<h4 id="amazon-s3">Amazon S3</h4>
<p>Amazon S3 is an Object <strong>Storage</strong> service that provides virtually unlimited storage for all your file storage needs. As an added feature, you can configure a storage pool(called a "bucket") to host and serve a static site to the internet.</p>
<h4 id="amazon-cloudfront">Amazon CloudFront</h4>
<p>Amazon CloudFront is a global <strong>CDN(Content Delivery Network)</strong> service that caches and serves files &amp; data to anyone globally very quickly and under low latency.</p>
<p>The idea of a CDN is to distribute commonly accessed files to edge servers around the world for quicker access by users near to the edge servers location. A shorter distance means that requests are being fulfilled faster. Hence, this increases the overall performance and less waiting time for the users.</p>
<p>Since static website contents are made up of static files that don't usually change much, the above two AWS services are perfect for setting up a high-performance static website at a very low overhead in terms of cost and technical requirements.</p>
<h2 id="prerequisite">🖇 Prerequisite</h2>
<p>To get started, you will need the following:</p>
<ol>
<li>An account with AWS</li>
<li>The files for your static website</li>
<li>A domain name(optional)</li>
</ol>
<p>You can sign up for an AWS account for free. However, to be eligible for <a target="_blank" href="https://aws.amazon.com/free/">AWS FREE Tier</a>, there are few criteria to take note of:</p>
<ul>
<li>Your AWS account has to be less than 12 months old</li>
<li>Your static website files have to be less than 5 GB in total, and less than 2,000 files (<em>Amazon S3</em>)</li>
<li>Monthly bandwidth not exceeding 50 GB and 2,000,000 HTTP/S requests. (<em>Amazon CloudFront</em>)</li>
</ul>
<p>Otherwise, you <strong>will be charged</strong> for the service used on your account.</p>
<h3 id="step-1-creating-a-bucket-on-amazon-s3">Step 1 - Creating a Bucket on Amazon S3</h3>
<p>a) After logging in to your AWS Console, under <strong>Services</strong> &gt; <strong>All Services</strong>, search for "<em>S3</em>" and click on it to go to the S3 dashboard.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605445283105/WXPGTVNKR.png" alt="Screenshot 2020-11-15 at 9.01.14 PM.png" /></p>
<p>b) On the S3 Dashboard, click on the <strong>Create Bucket</strong> button to create a new storage bucket to store your website files.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605445689189/kmJ2Vu9vl.png" alt="Screenshot 2020-11-15 at 9.04.08 PM.png" /></p>
<p>c) Enter a bucket name. The name has to be globally unique, so just try another one if you get an error "<em>Bucket with the same name already exists</em>".</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605445871549/i-DssOLz4.png" alt="Screenshot 2020-11-15 at 9.11.05 PM.png" /></p>
<blockquote>
<p>❗️ The <strong>Region</strong> does not matter much as we will be using Amazon CloudFront to distribute the files globally later.</p>
</blockquote>
<p>d) Since we want to allow anyone to view our website, clear the checkbox for <strong>Block <em>all</em> public access</strong>, and make sure to check the <strong>I acknowledge...</strong> statement checkbox below it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605449613198/aw1EGKmFE.png" alt="Screenshot 2020-11-15 at 10.11.37 PM.png" /></p>
<p>e) Leave all other settings intact and click on the <strong>Create bucket</strong> button at the bottom of the page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605446075717/n6IdlBoLs.png" alt="Screenshot 2020-11-15 at 9.13.25 PM.png" /></p>
<h3 id="step-2-upload-website-files-to-s3-bucket">Step 2 - Upload Website Files to S3 Bucket</h3>
<p>a) After creating a new bucket, click on the bucket name to view the bucket details.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605446419594/p8uxZnUZ6.png" alt="Screenshot 2020-11-15 at 9.16.09 PM.png" /></p>
<p>b) Scroll to the bottom of the page. You should see that there aren't any objects(files) in the new bucket. Click on the <strong>Upload</strong> button to upload some files.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605446592207/n0Z2kbxk4.png" alt="Screenshot 2020-11-15 at 9.21.20 PM.png" /></p>
<p>c) On the File Upload page, click on the <strong>Add files</strong> to add a file from your computer, or <strong>Add folder</strong> to add multiple files from a folder.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605447092830/OOoXH0zXV.png" alt="Screenshot 2020-11-15 at 9.29.17 PM.png" /></p>
<p>As you can see from the screenshot above, I've added some HTML &amp; CSS files, as well as few images in a folder, and a Javascript file.</p>
<p>Make sure you have added all the necessary files for your static website.</p>
<p>d) Leave all other settings intact, scroll to the bottom of the page and click on the <strong>Upload</strong> button to start the uploading of the files you have added above.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605447468488/GuJIJf-9M.png" alt="Screenshot 2020-11-15 at 9.33.47 PM.png" /></p>
<p>e) Depending on your connection and the number of files, you will have to wait while the files are being uploaded to the S3 Bucket:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605447555869/KF-UiYSX4.png" alt="Screenshot 2020-11-15 at 9.38.16 PM.png" /></p>
<p>f) Once the uploading process has completed, you may check if each file has been uploaded successfully or if there are any errors. Otherwise, just click on the <strong>Exit</strong> button to return to the Bucket details screen.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605447765722/24DVfafdG.png" alt="Screenshot 2020-11-15 at 9.41.20 PM.png" /></p>
<h3 id="step-3-enabling-static-website-hosting-on-s3-bucket">Step 3 - Enabling Static Website Hosting on S3 Bucket</h3>
<p>a) On the Bucket overview page, click on the <strong>Properties</strong> tab and scroll down.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605448229567/4jlNChYIc.png" alt="Screenshot 2020-11-15 at 9.47.11 PM.png" /></p>
<p>b) Look for <strong>Static website hosting</strong> section and click on the <strong>Edit</strong> button.</p>
<p>c) Select <strong>Enable</strong> option for <strong>Static website hosting</strong> setting and you will be presented with more options as the following screenshot:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605448817539/o0cNzqN5V.png" alt="Screenshot 2020-11-15 at 9.57.09 PM.png" /></p>
<ul>
<li><p><strong>Hosting type</strong>: Select <em>Host a static website</em></p>
</li>
<li><p>Enter the <strong>Index document</strong> file name(case-sensitive) that will serve as the home page for your static website. This is usually "<em>index.html</em>" by convention.</p>
</li>
<li><p>Enter the <strong>Error document</strong> that will be shown when an error occurs. E.g. "<em>error.html</em>", "<em>404.html</em>" etc.</p>
</li>
</ul>
<p>d) Scroll to the bottom of the page and click on the <strong>Save changes</strong> button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605449128348/rqcOv2yeF.png" alt="Screenshot 2020-11-15 at 10.04.25 PM.png" /></p>
<h3 id="step-4-unblock-public-access-settings">Step 4 - Unblock Public Access Settings</h3>
<p>a) On the Amazon S3 page, click on <strong>Account settings for Block Public Access</strong> from the left menu.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605451577557/xsVEtYRJa.png" alt="Screenshot 2020-11-15 at 10.41.08 PM.png" /></p>
<p>b) Click on the <strong>Edit</strong> button.</p>
<p>c) Clear the checkbox for <strong>Block <em>all</em> public access</strong>, and click on the <strong>Save changes</strong> button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605451790083/4ykjI8nMF.png" alt="Screenshot 2020-11-15 at 10.46.47 PM.png" /></p>
<p>d) When you're asked to confirm your action, enter "<em>confirm</em>" and click on the <strong>Confirm</strong> button to save your changes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605451851903/tYQ5i_VrY.png" alt="Screenshot 2020-11-15 at 10.50.23 PM.png" /></p>
<h3 id="step-5-add-a-bucket-policy">Step 5 - Add a Bucket Policy</h3>
<p>Before anyone could access the website on the bucket, we need to explicitly tell S3 Bucket to allow that.</p>
<p>a) On the Bucket overview page, click on the <strong>Permissions</strong> tab, scroll down and look for <strong>Bucket policy</strong> section.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605450107245/aaRDqHkOg.png" alt="Screenshot 2020-11-15 at 10.18.41 PM.png" /></p>
<p>b) On the <strong>Bucket policy</strong> section, click on the <strong>Edit</strong> button.</p>
<p>c) To grant public read access for your website on this bucket, copy the following bucket policy, and paste it in the Bucket policy editor below. For the "<em>Resource</em>", modify the <strong>my-static-website</strong> to your bucket name.</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"Version"</span>: <span class="hljs-string">"2012-10-17"</span>,
    <span class="hljs-attr">"Statement"</span>: [
        {
            <span class="hljs-attr">"Sid"</span>: <span class="hljs-string">"PublicReadGetObject"</span>,
            <span class="hljs-attr">"Effect"</span>: <span class="hljs-string">"Allow"</span>,
            <span class="hljs-attr">"Principal"</span>: <span class="hljs-string">"*"</span>,
            <span class="hljs-attr">"Action"</span>: [
                <span class="hljs-string">"s3:GetObject"</span>
            ],
            <span class="hljs-attr">"Resource"</span>: [
                <span class="hljs-string">"arn:aws:s3:::my-static-website/*"</span>
            ]
        }
    ]
}
</code></pre>
<blockquote>
<p>❗️ You must change the <strong>my-static-website</strong> to your bucket name.</p>
</blockquote>
<p>d) Click on the <strong>Save changes</strong> button when you are done.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605449128348/rqcOv2yeF.png" alt="Screenshot 2020-11-15 at 10.04.25 PM.png" /></p>
<p>e) You should now be able to access your website via the Amazon S3 Endpoint on the internet.</p>
<ul>
<li>Return to the Bucket overview page</li>
<li>Click on the <strong>Properties</strong> tab</li>
<li>Scroll to the bottom and look for <strong>Static website hosting</strong> section</li>
<li>You can find the URL under <strong>Bucket website endpoint</strong>. Click on it to view your website.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605451044244/MW8F5q8ec.png" alt="Screenshot 2020-11-15 at 10.34.43 PM.png" /></p>
<p>You have completed all the necessary steps for Amazon S3.</p>
<hr />

<h3 id="step-6-creating-a-cloudfront-distribution">Step 6 - Creating a CloudFront Distribution</h3>
<p>a) From the top navigation under <strong>Services</strong> &gt; <strong>All Services</strong>, search for "<em>CloudFront</em>" and click on it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605459960097/0UjlEsgkL.png" alt="Screenshot 2020-11-15 at 11.22.05 PM.png" /></p>
<p>b) On the CloudFront Distributions page, click on the <strong>Create Distribution</strong> button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605453934080/7xgLt29Jw.png" alt="Screenshot 2020-11-15 at 11.24.32 PM.png" /></p>
<p>c) Choose <strong>Web</strong> distribution delivery method and click on <strong>Get Started</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605454050988/dgB-KhX00.png" alt="Screenshot 2020-11-15 at 11.26.10 PM.png" /></p>
<p>d) On the <strong>Create Distribution</strong> page &gt; under <strong>Origin Settings</strong> section &gt; for <strong>Origin Domain Name</strong> &gt; enter the Amazon S3 website endpoint from <strong>Step 5 e)</strong> earlier. Example: <code>my-static-website.s3-website-us-east-1.amazonaws.com</code> (You could also choose from the drop-down list if available)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605454847513/BLG1Wjiu2.png" alt="Screenshot 2020-11-15 at 11.40.21 PM.png" /></p>
<p>The <strong>Origin ID</strong> should be automatically filled up for you. Leave other settings to the default under this section.</p>
<p>e) Leave other settings under <strong>Default Cache Behavior Settings</strong> section to the default.</p>
<p>f) Under <strong>Distribution Settings</strong> section &gt; for <strong>Alternate Domain Names
(CNAMEs)</strong>, you may insert your own domain name if you have one.</p>
<p>According to AWS, <em>You must list any custom domain names (for example, <code>www.example.com</code>) that you use in addition to the CloudFront domain name (for example, <code>d1234.cloudfront.net</code>) for the URLs for your files. Specify up to 100 CNAMEs separated with commas or put each on a new line. You also must create a CNAME record with your DNS service to route queries for <code>www.example.com</code> to <code>d1234.cloudfront.net</code>. For more information, see the Help.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605455578849/JcCPUkf_h.png" alt="Screenshot 2020-11-15 at 11.52.10 PM.png" /></p>
<p>g) If you wish to use a custom domain name, you must also provide an <strong>SSL Certificate</strong> for that domain under the next setting via <em>AWS Certificate Manager(ACM)</em> service.</p>
<blockquote>
<p>❗️ Creating a public certificate from Amazon is free of charge. However, this guide will not cover that part.</p>
</blockquote>
<p>Otherwise, select <strong>Default CloudFront Certificate (<code>*.cloudfront.net</code>)</strong> for the <strong>SSL Certificate</strong> setting.</p>
<p>h) Enter "<em>index.html</em>" for <strong>Default Root Object</strong>. This file name must match with the <strong>Index document</strong> file name entered in <strong>Step 3 c)</strong> previously on S3.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605456785136/nqR4rtM9p.png" alt="Screenshot 2020-11-16 at 12.07.05 AM.png" /></p>
<p>i) Leave other settings intact. Scroll to the bottom and click on the <strong>Create Distribution</strong> button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605456212123/inZqVxrGX.png" alt="Screenshot 2020-11-15 at 11.54.46 PM.png" /></p>
<p>j) As you remember from earlier, CloudFront will now distribute all your files to edge servers around the world. This process will take some time to complete.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605456344977/TLNZ1qa2b.png" alt="Screenshot 2020-11-15 at 11.56.59 PM.png" /></p>
<p>k) Once <em>Status</em> has updated to <strong>Deployed</strong>, the distribution is ready to be accessed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605456624205/8EJgntKqc.png" alt="Screenshot 2020-11-16 at 12.02.18 AM.png" /></p>
<p>l) The URL for the distribution can be found under the <strong>Domain Name</strong> column. For example: <code>stuvwxyz6789.cloudfront.net</code>. Hopefully, you will see your static website by accessing this URL on your web browser.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605457739184/QPwuYRgL1.png" alt="Screenshot 2020-11-16 at 12.28.37 AM.png" /></p>
<blockquote>
<p>❗️ If you have configured to use a custom domain name in <strong>Step 6 f)</strong>, you should be able to access your static website via the custom domain name instead.</p>
</blockquote>
<hr />

<h2 id="conclusion">🏁 Conclusion</h2>
<h3 id="congratulations">🎉 <strong>Congratulations!</strong> 🎊</h3>
<p>You have successfully setup a high performance and highly available static website on AWS using Amazon S3 and Amazon CloudFront. All these without setting up a single server too! 👏</p>
<p>If you are using a new AWS account, this will most likely cost you nothing as well.</p>
<p>In case you are worried about exceeding the FREE Tier usage, I'll share my usage cost over a three month period.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1605458273315/joSwy50XD.png" alt="Screenshot 2020-11-16 at 12.37.34 AM.png" /></p>
<p>As you can see, there's not much traffic to my site. Therefore, the daily cost hardly exceeds the FREE Tier. Even when it did, the cost came to only a cent at most.</p>
<hr />

<p>❗️ Thank you for reading! I hope you learnt something new today. If you find this useful, feel free to share it with your followers. 🦊</p>
<h2 id="references">📚 References</h2>
<ul>
<li>https://aws.amazon.com/free/</li>
<li>https://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Laravel Artisan Cache Commands Explained]]></title><description><![CDATA[Often times, when you are in the middle of developing a Laravel application, you may find that the changes you made in your code are not reflecting well on the application when testing.
Usually, the case is most likely caused by caching applied by th...]]></description><link>https://mono.my/laravel-artisan-cache-commands-explained</link><guid isPermaLink="true">https://mono.my/laravel-artisan-cache-commands-explained</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[cache]]></category><dc:creator><![CDATA[KenFai]]></dc:creator><pubDate>Sun, 08 Nov 2020 13:58:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1604843891649/l8PN9n-dP.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Often times, when you are in the middle of developing a Laravel application, you may find that the changes you made in your code are not reflecting well on the application when testing.</p>
<p>Usually, the case is most likely caused by caching applied by the Laravel framework.</p>
<p>Here are some of the common commands you can run in your terminal to alleviate the issue.</p>
<blockquote>
<p>❗️ Make sure you are running them in the context of your application. Meaning, your terminal is currently in the same directory as your Laravel application.</p>
</blockquote>
<h2 id="1-configuration-cache">1. Configuration Cache</h2>
<p>Caching configuration helps with combining all of the configuration options for your application into a single file which will be loaded quickly by the framework.</p>
<h3 id="clearing-configuration-cache">Clearing Configuration Cache</h3>
<p>However, if you notice changes to the configuration values in <code>.env</code> file is not reflecting on your application, you may want to consider clearing the configuration cache with the following command:</p>
<pre><code>$ php artisan config:clear
<span class="hljs-keyword">Configuration</span> <span class="hljs-keyword">cache</span> cleared!
</code></pre><p>If you want to quickly reset your configuration cache after clearing them, you may instead run the following command:</p>
<pre><code>$ php artisan config:<span class="hljs-keyword">cache</span>
Configuration <span class="hljs-keyword">cache</span> cleared!
Configuration cached successfully!
</code></pre><p>Caching your configuration will also help clear the current configuration cache. So it helps save your time without having to run both commands.</p>
<h2 id="2-route-caching">2. Route Caching</h2>
<p>Caching your routes will drastically decrease the amount of time it takes to register all of your application's routes. When you add a new route, you will have to clear your route cache for the new route to take effect.</p>
<h3 id="clearing-route-cache">Clearing Route Cache</h3>
<p>The following command will clear all route cache in your application:</p>
<pre><code>$ php artisan route:clear
Route <span class="hljs-keyword">cache</span> cleared!
</code></pre><p>To cache your routes again, simply run the following command:</p>
<pre><code>$ php artisan route:<span class="hljs-keyword">cache</span>
Route <span class="hljs-keyword">cache</span> cleared!
Routes cached successfully!
</code></pre><p>Again, running the above command alone is enough to clear your previous route cache and rebuild a new one.</p>
<h2 id="3-views-caching">3. Views Caching</h2>
<p>Views are cached into compiled views to increase performance when a request is made. By default, Laravel will determine if the uncompiled view has been modified more recently than the compiled view, before deciding if it should recompile the view.</p>
<h3 id="clearing-view-cache">Clearing View Cache</h3>
<p>However, if for some reason your views are not reflecting recent changes, you may run the following command to clear all compiled views cache:</p>
<pre><code>$ php artisan <span class="hljs-keyword">view</span>:clear
Compiled views cleared!
</code></pre><p>In addition, Laravel also provides an Artisan command to precompile all of the views utilized by your application. Similarly, the command also clears the view cache before recompiling a new set of views:</p>
<pre><code>$ php artisan <span class="hljs-keyword">view</span>:<span class="hljs-keyword">cache</span>
Compiled views cleared!
Blade templates cached successfully!
</code></pre><h2 id="4-events-cache">4. Events Cache</h2>
<p>If you are using Events in your Laravel application, it is recommended to cache your Events, as you likely do not want the framework to scan all of your listeners on every request.</p>
<h3 id="clearing-events-cache">Clearing Events Cache</h3>
<p>When you want to clear your cached Events, you may run the following Artisan command:</p>
<pre><code>$ php artisan <span class="hljs-symbol">event:</span>clear
Cached events cleared!
</code></pre><p>Likewise, caching your Events also clear any existing cache in the framework before a new cache is rebuilt:</p>
<pre><code>$ php artisan event:<span class="hljs-keyword">cache</span>
Cached <span class="hljs-keyword">events</span> cleared!
<span class="hljs-keyword">Events</span> cached successfully!
</code></pre><h2 id="5-application-cache">5. Application Cache</h2>
<p>Using Laravel's Cache is a great way to speed up frequently accessed data in your application. While developing your application involving cache, it is important to know how to flush all cache correctly to test if your cache is working properly.</p>
<h3 id="clearing-application-cache">Clearing Application Cache</h3>
<p>To clear your application cache, you may run the following Artisan command:</p>
<pre><code>$ php artisan <span class="hljs-keyword">cache</span>:<span class="hljs-keyword">clear</span>
Application <span class="hljs-keyword">cache</span> cleared!
</code></pre><p>This will clear all the cache data in storage which are typically stored in <code>/storage/framework/cache/data/</code>. The effect is similar to calling the <code>Cache::flush();</code> Facade method via code.</p>
<blockquote>
<p>❗️ This command will NOT clear any <strong>config</strong>, <strong>route</strong>, or <strong>view</strong> cache, which are stored in <code>/bootstrap/cache/</code> directory.</p>
</blockquote>
<h2 id="6-clearing-all-cache">6. Clearing All Cache</h2>
<p>Laravel provides a handy Artisan command that helps clear <em>ALL</em> the above caches that we have covered above. It is a convenient way to reset all cache in your application, without having to run multiple commands introduced before.</p>
<p>To clear all Laravel's cache, just run the following command:</p>
<pre><code>$ php artisan <span class="hljs-keyword">optimize</span>:<span class="hljs-keyword">clear</span>
<span class="hljs-keyword">Compiled</span> views cleared!
Application <span class="hljs-keyword">cache</span> cleared!
Route <span class="hljs-keyword">cache</span> cleared!
Configuration <span class="hljs-keyword">cache</span> cleared!
<span class="hljs-keyword">Compiled</span> services <span class="hljs-keyword">and</span> packages files removed!
Caches cleared successfully!
</code></pre><p>As you can read from the terminal feedback, all cache types that existed in your Laravel application will be cleared entirely, except Events cache.</p>
<blockquote>
<p>❗️ The <code>Compiled services and packages files removed!</code> can be run individually via <code>$ php artisan clear-compiled</code> command. It is used to remove the compiled class file in the framework.</p>
</blockquote>
<h2 id="bonus">🎁 Bonus</h2>
<p>If the above Laravel's Artisan commands don't seem to resolve the issue you are facing, you may need to look at other related environments in your project that may be causing it.</p>
<p>When building a Laravel project, it is common to employ the Composer Dependency Manager for PHP, as well as NPM for any JavaScript library that might be needed in your project. We just have to take note that both package managers are using some form of caching for performance improvements.</p>
<h3 id="clearing-composer-cache">Clearing Composer Cache</h3>
<p>Sometimes, a new package you just installed via Composer doesn't appear to be working at all. Or a new project you just cloned from a repository doesn't seem to be running correctly.</p>
<p>Such issues are usually caused by classmap error from a newly installed library class, or the cached version of a particular library does not match with the ones required by the project codebase you just cloned. In such a situation, you need to update the PHP autoloader by running the following command:</p>
<pre><code>$ composer <span class="hljs-keyword">dump</span>-autoload
</code></pre><p>As well as any of the following variations(they all achieve the same purpose of deleting all content from Composer's cache directories):</p>
<pre><code>$ composer clear-cache
$ composer clearcache
$ composer cc
</code></pre><h3 id="clearing-npm-cache">Clearing NPM Cache</h3>
<p>To clear NPM cache:</p>
<pre><code>$ npm <span class="hljs-keyword">cache</span> clean <span class="hljs-comment">--force</span>
</code></pre><h2 id="summary">🏁 Summary</h2>
<p>That's all you need to know about Laravel's Artisan cache related commands, as well as few additional commands for Composer and NPM, which you most likely will be using together in a Laravel project.</p>
<p>Here's a final command before we end the article:</p>
<pre><code>$ php artisan <span class="hljs-keyword">list</span>
</code></pre><p>The above command will list down all available Artisan commands when executed. I recommend you to take a look at it, as you might find something new that will be useful to you!</p>
<p>❗️ Thank you for reading! If you find this useful, feel free to share it with your followers. 🦊</p>
<h2 id="references">📚 References</h2>
<ul>
<li>https://laravel.com/docs/6.x/configuration#configuration-caching</li>
<li>https://laravel.com/docs/8.x/controllers#route-caching</li>
<li>https://laravel.com/docs/8.x/views#optimizing-views</li>
<li>https://laravel.com/docs/8.x/events#event-discovery</li>
<li>https://laravel.com/docs/8.x/cache#removing-items-from-the-cache</li>
<li>https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload-</li>
<li>https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-cc</li>
<li>https://docs.npmjs.com/cli/v6/commands/npm-cache</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Understanding NGINX Location Block Modifiers]]></title><description><![CDATA[🛠 NGINX  has become the preferred default web server in recent years. Although it has many benefits over other popular web servers, a poorly configured web server may bring more harm than the benefits that it supposed to provide.
This guide assumes ...]]></description><link>https://mono.my/understanding-nginx-location-block-modifiers</link><guid isPermaLink="true">https://mono.my/understanding-nginx-location-block-modifiers</guid><category><![CDATA[nginx]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[KenFai]]></dc:creator><pubDate>Sun, 01 Nov 2020 16:49:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1604249328543/t2CCygcJv.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>🛠 <a target="_blank" href="http://nginx.org/">NGINX</a>  has become the preferred default web server in recent years. Although it has many benefits over other popular web servers, a poorly configured web server may bring more harm than the benefits that it supposed to provide.</p>
<p>This guide assumes that you have some experience in setting up a web server, understand the purpose of URL rewrites, and basic knowledge of regular expression (RegEx). We will go over all the options available when using the <code>location</code> directive, as well as some practical examples to highlight common use cases.</p>
<p>You may scroll to the bottom for a quick <strong>TL;DR</strong> summary. 📘</p>
<h2 id="the-location-block">📬 The <code>location</code> block</h2>
<p>One of the most widely used configuration modules within NGINX is the <code>location</code> block. It is responsible for matching the Request URI and to subsequently passes it on to the correct block for further processing.</p>
<p>The <code>location</code> directive only concern about the path after the domain name and before any query strings. For example, if we have the following URL:</p>
<p><code>https://www.example.com/user/posts?post_id=45#top</code></p>
<p>The <code>location</code> directive will only use the path <code>/user/posts</code> to perform any matching.</p>
<h3 id="1-match-any-urls">1. Match any URLs</h3>
<p>Let's start with a common example that you have likely encountered in many default configurations and tutorials elsewhere:</p>
<pre><code><span class="hljs-attribute">location</span> / {
    <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$uri</span> <span class="hljs-variable">$uri</span>/ =<span class="hljs-number">404</span>;
}
</code></pre><p>The above rule will match with any URLs, and try to serve them. For example, it will match with <code>https://www.example.com/homepage.html</code> and try to serve the page if it exists. Otherwise, it will return a <em>404 not found</em> response.</p>
<p>Just to be clear, the <code>/</code> after <code>location</code> is not a special symbol. It is actually telling NGINX to match with any URIs that <strong>begins</strong> with <code>/</code>, which literally means to match with any URLs since that's the first character after a domain name.</p>
<p>This specific rule usually serves as a fallback that matches last when all other <code>location</code> blocks failed to match.</p>
<blockquote>
<p>❗️ Please note that the keyword here is <strong>begins</strong>. NGINX will always match from the beginning of a URI no matter which rule is being applied.</p>
</blockquote>
<h3 id="2-match-exact-url">2. Match exact URL</h3>
<p>Sometimes, we would like to match an exact URL for NGINX to process separately. For example, redirecting legacy links after a migration or a marketing landing page such as <code>https://www.example.com/deals</code>.</p>
<p>To do that, the <code>location</code> directive offers us a handy <code>=</code> modifier to be used as such:</p>
<pre><code><span class="hljs-attribute">location</span> = /deals {
    <span class="hljs-attribute">return</span> <span class="hljs-number">301</span> https://<span class="hljs-variable">$host</span>/marketing/deal/2020;
}
</code></pre><p>With the <code>=</code> modifier set, NGINX will only redirect to <code>https://example.com/marketing/deal/2020</code> if the request URL is exactly <code>https://example.com/deals</code>.</p>
<p>❗️ <em>It is possible to achieve the same outcome with <code>location = deals { ... }</code> but it's better to express your intent in a clear manner.</em></p>
<h3 id="3-matching-url-prefix">3. Matching URL prefix</h3>
<p>Continuing on with the example before, what if the <code>=</code> modifier was not set?</p>
<pre><code><span class="hljs-attribute">location</span> /deals {
    <span class="hljs-attribute">return</span> <span class="hljs-number">301</span> https://<span class="hljs-variable">$host</span>/marketing/deal/2020;
}
</code></pre><p>In such configuration, NGINX will redirect to <code>https://example.com/marketing/deal/2020</code> as long as the URL <strong>begins</strong> with <code>/deals</code>. The following URLs will be considered a match:</p>
<ul>
<li><code>https://example.com/deals</code></li>
<li><code>https://example.com/deals/today</code></li>
<li><code>https://example.com/deals/today/12</code></li>
<li><code>https://example.com/dealsoftheday</code></li>
<li><code>https://example.com/dealsoftheday/latest</code></li>
<li><code>https://example.com/dealsoftheday/latest?deal_id=12</code></li>
</ul>
<p>A prefix match is more useful when you have a dynamic page that you want to serve via preprocessing engine such as PHP:</p>
<pre><code><span class="hljs-attribute">location</span> /post {
    <span class="hljs-attribute">rewrite</span><span class="hljs-regexp"> ^/post/(.*)$</span> /post.php?post_id=<span class="hljs-variable">$1</span>;
}
</code></pre><p>With that in place, the <code>post.php</code> page can be accessed from the URL <code>https://example.com/post/23</code> where the value <code>23</code> will be used as the <code>post_id</code> variable for PHP to serve the correct post content.</p>
<p>Another practical use case would be to deny access to a certain directory on your server. For example, you wish to deny access to a directory that contains your users' uploaded profile photos in <code>/upload/</code> directory. Here's how you can apply such restriction:</p>
<pre><code><span class="hljs-keyword">location</span> /upload/ {
    deny <span class="hljs-keyword">all</span>;
}
</code></pre><p>This may do the job well for now until you have more matching cases to deal with. We will reexamine this further down with a more suitable modifier.</p>
<h3 id="4-match-urls-using-regular-expression">4. Match URLs using regular expression</h3>
<p>Another common use case is when you want to serve static assets such as images to your visitors directly without having to route the request through your web application. NGINX is best known for serving static content efficiently. Therefore, it is wise to take advantage of such a feature.</p>
<p>By using the <code>~</code> modifier, it will allow us to define a configuration with regular expression to serve a collection of asset types, and handle it properly when they are not found.</p>
<pre><code><span class="hljs-attribute">location</span> <span class="hljs-regexp">~ \.(ico|gif|jpe?g|png|js|css)$</span> {
    <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$uri</span> =<span class="hljs-number">404</span>;
}
</code></pre><p>As you can see from above, having the full power of regular expression makes matching URL pattern very convenient.</p>
<p>❗️ <em>However, I want to stress that the above configuration is only beneficial if you have another <strong>match all</strong> <code>location</code> block that matches all URIs. If your site only serves static content, there's no need for such configuration.</em></p>
<h3 id="5-case-insensitivity-in-regular-expression-matching">5. Case-insensitivity in regular expression matching</h3>
<p>The previous example works well if all your assets have a lower-case file extension. To cover both lower-case and upper-case file extensions, we can use the <code>~*</code> modifier for case-insensitive matching.</p>
<pre><code><span class="hljs-attribute">location</span> <span class="hljs-regexp">~* \.(ico|gif|jpe?g|png|js|css)$</span> {
    <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$uri</span> =<span class="hljs-number">404</span>;
}
</code></pre><p>This will match <code>https://www.example.com/images/teapot.jpg</code> as well as <code>https://www.example.com/images/teapot.JPG</code>.</p>
<blockquote>
<p>❗️ Please note that this does not mean that a file named 'teapot.jpg' can be accessed by the URL request <code>https://www.example.com/images/teapot.JPG</code>. Instead, it is the underlying OS filesystem that decides whether a file can be served with a case-insensitive request or not, not NGINX.</p>
</blockquote>
<h3 id="6-prioritising-prefix-matching">6. Prioritising prefix matching</h3>
<p>With multiple <code>location</code> blocks in place, how would NGINX decide which block gets matched with the request URI? Here's the order with what we've learned so far:</p>
<ol>
<li>NGINX will always look for an exact match that uses the <code>=</code> modifier.</li>
<li>If there is no exact match, NGINX will try to match a RegEx <code>location</code> block that uses the <code>~</code> &amp; <code>~*</code> modifiers, in top-down order.</li>
<li>If there is no RegEx block match, NGINX will try to match the longest prefix <code>location</code> block that doesn't contain any modifiers.</li>
</ol>
<p>Now, what if you want to match a prefix <code>location</code> block(step 3) <strong>first</strong> with the presence of other RegEx blocks(step 2)?</p>
<p>Remember the previous prefix match example that was used to deny access to a directory? Here's what we have before:</p>
<pre><code><span class="hljs-keyword">location</span> /upload/ {
    deny <span class="hljs-keyword">all</span>;
}
</code></pre><p>And here's the previous RegEx <code>location</code> block example for serving static assets:</p>
<pre><code><span class="hljs-attribute">location</span> <span class="hljs-regexp">~* \.(ico|gif|jpe?g|png|js|css)$</span> {
    <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$uri</span> =<span class="hljs-number">404</span>;
}
</code></pre><p>However, since RegEx <code>location</code> blocks will be matched before URL prefix matches, the URL prefix block will never be matched. Therefore, anyone will be able to access all resources in the <code>/upload/</code> directory.</p>
<p>To solve this issue, that's where you can use the <code>^~</code> modifier to prioritise a <code>location</code> block to be matched before other potentially matching RegEx blocks. In fact, NGINX will not perform any further RegEx <code>location</code> block matching checks if a <code>^~</code> modifier <code>location</code> block is found to be a match.</p>
<p>Here's how you can redefine the URL prefix block that imposes access restriction to a specific directory path:</p>
<pre><code><span class="hljs-attribute">location</span><span class="hljs-regexp"> ^~</span> /upload/ {
    <span class="hljs-attribute">deny</span> all;
}
</code></pre><p>With the above configuration, any request to the resources in <code>/upload/</code> will be denied with a 403 Forbidden response, such as <code>https://www.example.com/upload/profile23.jpg</code>.</p>
<h3 id="7-determining-the-order-of-location-matching">7. Determining the order of location matching</h3>
<p>That concludes the fourth and last modifier available for use in a <code>location</code> directive. Therefore, the final matching order is as follows:</p>
<ol>
<li>NGINX will look for an exact match that uses the <code>=</code> modifier.</li>
<li>If there is no exact match, NGINX will try to match the longest prefix <code>location</code> block that uses the <code>^~</code> modifier.</li>
<li>If there is no prefix match, NGINX will try to match a RegEx <code>location</code> block that uses the <code>~</code> &amp; <code>~*</code> modifiers, in top-down order.</li>
<li>If there is no RegEx block match, NGINX will try to match the longest prefix <code>location</code> block that doesn't contain any modifiers.</li>
</ol>
<p>❗️ <em>In reality, NGINX will first scan all non-RegEx based prefix <code>location</code> blocks(including <code>^~</code>) and remember them to be used later if no RegEx <code>location</code> blocks are matched. But in my opinion, it's easier to visualize them in just four steps as described above.</em></p>
<hr />

<h2 id="summary">🎁 Summary</h2>
<p>That's all to it about NGINX <code>location</code> directive and all <strong>four</strong> of its modifiers. Here's a table to summarize each modifier's purpose and effect.</p>
<table>
<thead>
<tr>
<td>Modifier</td><td>Purpose</td><td>Order</td><td>Example</td><td>Matching Request URI</td></tr>
</thead>
<tbody>
<tr>
<td><code>=</code></td><td>For matching an exact URI</td><td>First</td><td><code>location = /deals { ... }</code></td><td><code>https://ex.com/deals</code></td></tr>
<tr>
<td><code>^~</code></td><td>For matching URL prefix</td><td>Second</td><td><code>location ^~ /upload/ { ... }</code></td><td><code>https://ex.com/upload/profile34.jpg</code> <code>https://ex.com/upload/photos/andy2020.jpg</code></td></tr>
<tr>
<td><code>~</code></td><td>For matching URL with RegEx (case-sensitive)</td><td>Third</td><td><code>location ~ \.php$ { ... }</code></td><td><code>https://ex.com/index.php</code> <code>https://ex.com/index.php?route=home</code> <code>https://ex.com/blog/post.php?post_id=45</code></td></tr>
<tr>
<td><code>~*</code></td><td>For matching URL with RegEx (case-insensitive)</td><td>Fourth</td><td><code>location ~* .(gif|jpe?g|png|css|js)$ { ... }</code></td><td><code>https://ex.com/images/banner.jpg</code> <code>https://ex.com/gallery/cars/ROADSTER.JPEG</code> <code>https://ex.com/static/css/styles.css</code> <code>https://ex.com/scripts/jquery.js</code> <code>https://ex.com/favicon.PNG</code></td></tr>
<tr>
<td><em>none</em></td><td>For matching URL prefix</td><td>Last</td><td><code>location / { ... }</code></td><td><code>https://ex.com/</code> <code>https://ex.com/index.html</code> <code>https://ex.com/homepage.html</code> <code>https://ex.com/img/background.png</code> <code>https://ex.com/styles/footer.css</code> <code>https://ex.com/marketing/subscribe.php</code></td></tr>
</tbody>
</table>
<p>❗️Thank you for reading! If you find this useful, feel free to share it with your followers. 🦊</p>
<h2 id="references">📙 References</h2>
<p>To find out more about NGINX <code>location</code> directive and its technical details, you may read further from the following great resources.</p>
<ul>
<li>http://nginx.org/en/docs/http/ngx_http_core_module.html#location</li>
<li>https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms</li>
<li>https://www.keycdn.com/support/nginx-location-directive</li>
</ul>
]]></content:encoded></item></channel></rss>